This isn't a complete guide on HomeAssistant, MQTT, Mosquitto, C-Gate, Debian or anything else, but with the help of Google and luck, it should get you going..
Components used for the interface:
- Debian Linux ( https://www.debian.org/ )
- C-Gate for Linux ( http://www2.clipsal.com/cis/technical/downloads/c-gate )
- Mosquitto MQTT broker ( https://mosquitto.org/ )
- C-Gate / MQTT Source Code from ( https://github.com/the1laz/cgateweb )
- MQTT Node.js Stuff ( https://www.npmjs.com/package/mqtt )
- Forever for Node.js ( https://www.npmjs.com/package/forever )
www3.clipsal.com/cis/downloads/Toolkit/CGateServerGuide_1_0.pdf
http://addictedtopi.tumblr.com/post/96351714013/installing-c-gate-on-a-raspberry-pi
Setting up Debian:
Install other packages openjdk-7-jdk nodejs npm mosquitto mosquitto-clients
Setting up Node.js
npm install mqtt --save
C-Bus / C-Gate setup..
Install C-Gate on linux and connect to this remote C-Gate with the C-Bus toolkit, get everything working with this configuration BEFORE continuing.. The rest won't really work until you have networks / projects etc set up. The configuration is stored on the linux server with the C-Gate install.
I used this post as a guide to install:
http://addictedtopi.tumblr.com/post/96351714013/installing-c-gate-on-a-raspberry-pi
Modifications to the code required..
I assume the excellent C-Gate / MQTT code from the1las @ github was written for an older version of C-Gate than the one I'm using (2.10.6) so I had to make some modifications to get it to work for me.. Without this code as a base, this would have taken so much longer!
modified index.js
var mqtt = require('mqtt'), url = require('url'); var net = require('net'); var events = require('events'); var settings = require('./settings.js'); var buffer = ""; var eventEmitter = new events.EventEmitter(); // MQTT URL var mqtt_url = url.parse('mqtt://'+settings.mqtt); // Username and password var OPTIONS = {}; if(settings.mqttusername && settings.mqttpassword) { OPTIONS.username = settings.mqttusername; OPTIONS.password = settings.mqttpassword; } // Create an MQTT client connection var client = mqtt.createClient(mqtt_url.port, mqtt_url.hostname,OPTIONS); var HOST = settings.cbusip; var COMPORT = 20023; var EVENTPORT = 20025; var logging = settings.logging; // Connect to cgate via telnet var command = new net.Socket(); command.connect(COMPORT, HOST, function() { console.log('CONNECTED TO C-GATE COMMAND PORT: ' + HOST + ':' + COMPORT); command.write('EVENT ON\n'); }); // Connect to cgate event port via telnet var event = new net.Socket(); event.connect(EVENTPORT, HOST, function() { command.write('PROJECT LOAD '+settings.cbusname+'\n'); command.write('PROJECT USE '+settings.cbusname+'\n'); command.write('NET OPEN '+settings.cbusnetwork+'\n'); command.write('PROJECT START'+'\n'); console.log('CONNECTED TO C-GATE EVENT PORT: ' + HOST + ':' + EVENTPORT); }); client.on('connect', function() { // When connected console.log('CONNECTED TO MQTT: ' + settings.mqtt); // Subscribe to MQTT client.subscribe('cbus/write/#', function() { // when a message arrives, do something with it client.on('message', function(topic, message, packet) { if (logging == true) {console.log('Message received on ' + topic + ' : ' + message);} parts = topic.split("/"); if (parts.length > 5) switch(parts[5].toLowerCase()) { // Get updates from all groups case "getall": command.write('GET /'+parts[2]+'/'+parts[3]+'/* level\n'); break; // On/Off control case "switch": if(message == "ON") {command.write('ON '+parts[2]+'/'+parts[3]+'/'+parts[4]+'\n')}; if(message == "OFF") {command.write('OFF '+parts[2]+'/'+parts[3]+'/'+parts[4]+'\n')}; break; // Ramp, increase/decrease, on/off control case "ramp": switch(message.toUpperCase()) { case "INCREASE": eventEmitter.on('level',function increaseLevel(address,level) { if (address == parts[2]+'/'+parts[3]+'/'+parts[4]) { command.write('RAMP '+parts[2]+'/'+parts[3]+'/'+parts[4]+' '+Math.min((level+26),255)+' '+'\n'); eventEmitter.removeListener('level',increaseLevel); } }); command.write('GET '+parts[2]+'/'+parts[3]+'/'+parts[4]+' level\n'); break; case "DECREASE": eventEmitter.on('level',function decreaseLevel(address,level) { if (address == parts[2]+'/'+parts[3]+'/'+parts[4]) { command.write('RAMP '+parts[2]+'/'+parts[3]+'/'+parts[4]+' '+Math.max((level-26),0)+' '+'\n'); eventEmitter.removeListener('level',decreaseLevel); } }); command.write('GET '+parts[2]+'/'+parts[3]+'/'+parts[4]+' level\n'); break; case "ON": command.write('ON '+parts[2]+'/'+parts[3]+'/'+parts[4]+'\n'); break; case "OFF": command.write('OFF '+parts[2]+'/'+parts[3]+'/'+parts[4]+'\n'); break; default: var ramp = message.split(","); var num = Math.round(parseInt(ramp[0])*255/100) if (!isNaN(num) && num < 256) { if (ramp.length > 1) { command.write('RAMP '+parts[2]+'/'+parts[3]+'/'+parts[4]+' '+num+' '+ramp[1]+'\n'); } else { command.write('RAMP '+parts[2]+'/'+parts[3]+'/'+parts[4]+' '+num+'\n'); } } } break; default: } }); }); // publish a message to a topic client.publish('hello/world', 'CBUS ON', function() { }); }); command.on('data',function(data) { var lines = (buffer+data.toString()).split("\n"); buffer = lines[lines.length-1]; if (lines.length > 1) { for (i = 0;i// Add a 'data' event handler for the client socket // data is what the server sent to this socket event.on('data', function(data) { var parts = data.toString().split(" "); if(parts[0] == "lighting") { address = parts[2].split("/"); switch(parts[1]) { case "on": if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' ON');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' 100%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/state' , 'ON' ); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , '100' ); break; case "off": if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' OFF');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' 0%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/state' , 'OFF' ); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , '0' ); break; case "ramp": if(parseInt(parts[3]) > 0) { if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' ON');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' '+Math.round(parseInt(parts[3])*100/255).toString()+'%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/status' , 'ON', function() {}); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , Math.round(parseInt(parts[3])*100/255).toString(), function() {}); } else { if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' OFF');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' 0%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/status' , 'OFF' ); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , '0' ); } break; default: } } });1 && parts1[0] == "300") { var parts2 = parts1[1].toString().split(" "); address = (parts2[0].substring(0,parts2[0].length-1)).split("/"); var level = parts2[1].split("="); if (parseInt(level[1]) == 0) { if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' OFF');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' 0%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/state' , 'OFF' ); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , '0' ); eventEmitter.emit('level',address[3]+'/'+address[4]+'/'+address[5],0); } else { if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' ON');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' '+Math.round(parseInt(level[1])*100/255).toString()+'%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/state' , 'ON' ); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , Math.round(parseInt(level[1])*100/255).toString() ); eventEmitter.emit('level',address[3]+'/'+address[4]+'/'+address[5],Math.round(parseInt(level[1]))); } } else { var parts2 = parts1[0].toString().split(" "); if (parts2[0] == "300") { address = (parts2[1].substring(0,parts2[1].length-1)).split("/"); var level = parts2[2].split("="); if (parseInt(level[1]) == 0) { if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' OFF');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' 0%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/state' , 'OFF' ); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , '0' ); eventEmitter.emit('level',address[3]+'/'+address[4]+'/'+address[5],0); } else { if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' ON');} if (logging == true) {console.log('C-Bus status received: '+address[3] +'/'+address[4]+'/'+address[5]+' '+Math.round(parseInt(level[1])*100/255).toString()+'%');} client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/state' , 'ON' ); client.publish('cbus/read/'+address[3]+'/'+address[4]+'/'+address[5]+'/level' , Math.round(parseInt(level[1])*100/255).toString() ); eventEmitter.emit('level',address[3]+'/'+address[4]+'/'+address[5],Math.round(parseInt(level[1]))); } } } } } });
The modifications are based on the reading of the C-Gate docs.
Basically I added some stuff on connect to C-Gate.. e.g. the lines:
- PROJECT LOAD
- PROJECT USE
- NET OPEN
- PROJECT START
The remainder of the modifications were around device control, Basically you now send
ON Network/Application/Group
or
OFF Network/Application/Group
to the C-Gate and it turns on the appropriate C-Bus Group. The original code prefixed each command with the cbusname.. I also removed some of the parameters on the mqtt client.publish statements as they caused inconsistencies in my setup.
modified settings.js
//cbus ip address exports.cbusip = '127.0.0.1'; //cbus project name exports.cbusname = "HOME"; exports.cbusnetwork = "254"; //mqtt server ip:port exports.mqtt = '127.0.0.1:1883'; //username and password (unncomment to use) //exports.mqttusername = 'user1'; //exports.mqttpassword = 'password1'; //logging exports.logging = true;The only difference here was adding the cbus network variable.
Next was adding the MQTT config in HomeAssistant (configuration.yaml). This points at the IP address where you installed Mosquitto..
mqtt: broker: 192.168.X.X port: 1883 client_id: home-assistant-1 keepalive: 60 protocol: 3.1
This just allows MQTT to be used, it doesn't define anything to switch on / off...
I control some lights as follows (existing HUE lughts, then the C-Bus Lights):
light: - platform: hue host: 192.168.X.X - platform: mqtt name: Entrance (CBUS) state_topic: "cbus/read/254/56/2/state" command_topic: "cbus/write/254/56/2/switch" - platform: mqtt name: Outside Front (CBUS) state_topic: "cbus/read/254/56/3/state" command_topic: "cbus/write/254/56/3/switch" - platform: mqtt name: Lounge (CBUS) state_topic: "cbus/read/254/56/4/state" command_topic: "cbus/write/254/56/4/switch"
The command_topic / state topic contain the C-Bus Network/Application/Group being controlled
You can also add "Switches" rather than lights
switch: - platform: mqtt name: Mozzie Zapper (CBUS) state_topic: "cbus/read/254/56/11/state" command_topic: "cbus/write/254/56/11/switch"
The control on these is 2-way, so if i turn on a C-Bus light with the wall switch, the change is reflected in HomeAssistant.
To get the Network/Application/Group list for my C-Bus setup:
Look in the C-Bus Toolkit..
The Network number is displayed at the top. The Application number is displayed on the Application properties.
The Group Address is displayed on the Group to be controlled.
Starting all at boot:
C-Gate started as per this post
http://addictedtopi.tumblr.com/post/96351714013/installing-c-gate-on-a-raspberry-pi
The node.js code is loaded at boot on the debian box (jessie) by the forever module..
after doing an "npm install -g" to load the forever stuff for node.js, i had to create a symlink
ln -s /usr/bin/nodejs /usr/bin/node
or forever doesn't work (it looks for node, not nodejs).
Startup script /etc/init.d/nodeup
#!/bin/sh #/etc/init.d/nodeup export PATH=$PATH:/usr/local/bin export NODE_PATH=$NODE_PATH:/usr/local/lib/node_modules case "$1" in start) exec forever start --sourceDir=/usr/local/bin/nodejs -p /root/.forever/ index.js ;; stop) exec forever stop --sourceDir=/usr/local/bin/nodejs index.js ;; *) echo "Usage: /etc/init.d/nodeup {start|stop}" exit 1 ;; esac exit 0
Nothing special, just start the init script at boot as per usual..
The future...
Oneday I might modify the node.js code to discover all C-Bus applications and devices, but for now I can control my lights from anywhere and life is good!
Hey James,
ReplyDeleteHow do you physically connect the machine that runs C-gate to the C-Bus network?
I used one of these... https://www.clipsal.com/products/detail?catno=5500CN2
Delete(there was a load lots cheaper on ebay).
great
ReplyDeleteHi - thanks for posting the above, it saved me a bit of time. Just wondering if line 138 is corrupt or not? nodejs threw an error on it.
ReplyDeleteYeah, line 138 looks like a copy paste issue.
DeleteIt was / is. I did link to the original source, you can compare / fix :) I tried to edit but blogger hates me..
DeleteHey James - using your excellent instructions above I managed to get CBus going on a large network. However, I am having some problems which is explained here: https://github.com/the1laz/cgateweb/issues/20 . I needed to use the cgateweb from the1laz as the version above didn't work for me (version of cgate?) so I posted the issue on his Github. I am still waiting to hear a response (only put it up yesterday) but I was wondering if you might have an idea on what is going on? Interested in your thoughts. Thanks. Locky.
ReplyDeleteHey, I stopped using cgate / cbus a while ago so haven't looked at it much since I published the above. Apologies that I can't help more.
DeleteHi James, I'm going through a similar journey to what you have been through. I was thinking that ideally, these sort of fixes should go back into the cgateweb project on github. I'm just wondering what the barriers are and whether, assuming I get it working, I should go to the effort of forking the original code. I'm not really even sure of the process, but it's all learning :-)
ReplyDelete