I started with the question:
How to connect my gas heating system, which is driven by a Controller from Technische Alternative, to my OpenHAB server.
I tried to understand the former TA_CMI Binding. But it looks like that there is no development any more. That binding used CoE (CanOverEthernet) as an protocol.
During the time I was searching for a solution, my CMI got an update and was suddenly capable to speak modbus.
So I started immediately with my first connection tries an I learned very quickly that the integrated OH2 Modbus Binding only is capable to connect with Modbus Slaves. So I had to find out how to deal with a Modbus Master.
I started searching the internet to find out how to write a Modbus Slave by myself. There are lot solutions therefore out in the world. But most of them I was not capable to get to run on my PC nor on my RasPi.
Finally I found an very interesting solution with nodejs. And I decided to start creating a working solution with that.
I became therefore great support by lichtjaeger in the forum nodeforum.de (see thread: wie-modbus-tcp-anwenden )
In just about 8 days we developed (better lichtjaeger developed) a running solution for a modbus slave calling the openhab RestAPI on changes.
What do you have to do, to get a running solution as well?
1. Step - NodeJS
You have to install nodejs (>V6.8) on the raspberry pi (or your preffered running platform)
I 'am using a second raspberry Pi as Gateway between the ModbusMaster and my openHAB RaspberryPI.
It should also work if you install the nodejs on your openHAB RaspberryPI
2. Step - Script
Copy the nodejs script to your RasPi
I created therfore a folder
~/projekte/cmi_modbus
In that folder I created the script File “cmi_modbus.js” with following content
const net = require('net')
const modbus = require('modbus-tcp')
const {request} = require('http')
const url = require('url')
const server = net.createServer(socket => {
const modbusServer = new modbus.Server()
modbusServer.writer().pipe(socket)
socket.pipe(modbusServer.reader())
modbusServer.on("write-single-register", setItemVal) // checking for event "write-single-register" (FC06) an integer value is changed
modbusServer.on("write-single-coil", setItemVal) // checking for event "write-single-coil" (FC05) a coil state is changed
})
server.listen(1502, () => { // remember to redirect modbus port 502 to 1502
console.log('Server online!')
})
//still todo: Checking for Integer or Coil. The following code only works for write-single-register the functionname should be changed to setInteger and //setCoil, or you have to find out which kind of event called the function within the function
function setItemVal(addr, val, reply) {
console.log('writing:', addr, val)
putText(
`http://hab:8080/rest/items/MB_I_Reg_${addr > 9 ? addr : '0' + addr}/state`, // here the RestAPI String is build for the OH2 item MB_I_Reg_xx
val.readInt16BE(0).toString()
)
.then(result => console.log(result))
.catch(err => console.error(err))
.then(() => reply(null, val))
}
// general function to send the RestAPI call to OpenHAB
function putText(urlStr = '', text = '') {
return new Promise((ful, rej) => { // fulfill und reject
const req = request(Object.assign({
method: 'PUT',
headers: {
'Content-Type': 'text/plain',
'Accept': 'application/json',
'Content-Length': text.length
}
}, url.parse(urlStr)), res => {
let response = ''
res.on('data', chunk => {
response += chunk.toString()
})
res.on('end', () => {
ful(`Status: ${res.statusCode + '\n'}Body: ${response}`)
})
res.on('error', err => {
rej(`Fehler bei Antwort: ${err}`)
})
})
req.on('error', err => {
rej(`Abfrage-Fehler: ${err}`)
})
req.write(text) // here comes the call
req.end()
})
}
3. Step - Firewall
You have to redirect the port 502 to 1502.
You can do this by entering this comand:
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 502 -j REDIRECT --to-port 1502
If it should work after a reboot of you device you should add rc.local by using
sudo nano /etc/rc.local
following lines before the exit 0
#redirect Port 502 to 1502, to allow nodejs to hear on it
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 502 -j REDIRECT --to-port 1502
printf "Caution port 502 is redirekted to port 1502
exit 0
4. Step - Execute
Now you can start the script by calling
node CMI_Modbus.js
5. Step
Configure your Modbus Master Device. It should write with the modbus functional code fc06 to the ip adress of your RasPi.
Here an example of my Technische Alternative CMI configuration:
6. Step - Create openHAB .item File
Create the openhab modbus slave item file.
Here is my example file
Group gModbus "Modbus"
Group gModbusRegister "Modbus Register" (gModbus)
Group gModbusCoils "Modbus Coil" (gModbus)
Group gHeizzentrale "Dachgeschoss Heizzentrale"
Group gHeizung "Heizungswerte" (gHeizzentrale)
Group gPuffer "Pufferwerte" (gHeizzentrale)
Group gWQ "Waermequelle" (gHeizzentrale)
Number HzgAussenTemp "Aussentemperatur Heizung [%.1f °C]" <temperature> (Temperature, gHeizung)
Number SpitzbodenRT "Raumtemperatur Spitzboden [%.1f °C]" <temperature> (Temperature, gHeizung)
Number HzgVorlaufTemp "Vorlauftemperatur Heizung [%.1f °C]" <temperature> (Temperature, gHeizung)
Number HzgRuecklaufTemp "Ruecklauftemperatur Heizung [%.1f °C]" <temperature> (Temperature, gHeizung)
Number HzgSollVorlaufTemp "Sollwert Vorlauftemperatur Heizung [%.1f °C]" <temperature> (Temperature, gHeizung)
Number SP_Ladeleitung "Speicherladeleitung [%.1f °C]" <temperature> (Temperature, gPuffer)
Number SP_Kuppel "Speicherkuppel [%.1f °C]" <temperature> (Temperature, gPuffer)
Number SP_Kopf "Speicherkopf [%.1f °C]" <temperature> (Temperature, gPuffer)
Number SP_Reserve1 "Speicher Reserve1 [%.1f °C]" <temperature> (Temperature, gPuffer)
Number SP_Reserve2 "Speicher Reserve1 [%.1f °C]" <temperature> (Temperature, gPuffer)
Number RT_WQ1 "Ruecklauftemperatur WQ1 [%.1f °C]" <temperature> (Temperature, gWQ)
Number BT_WQ1 "Betriebstemperatur WQ1 [%.1f °C]" <temperature> (Temperature, gWQ)
// Here you create space were all the values are stored.
// this is because Modbus Slave is responsible for hosting
// Here we define all integer register which become written by the Modbus Master the number is equal registernumber
Number MB_I_Reg_01 "Modbus Register 01 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_02 "Modbus Register 02 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_03 "Modbus Register 03 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_04 "Modbus Register 04 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_05 "Modbus Register 05 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_06 "Modbus Register 06 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_07 "Modbus Register 07 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_08 "Modbus Register 08 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_09 "Modbus Register 09 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_10 "Modbus Register 10 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_11 "Modbus Register 11 raw value [%.1f]" (gModbusRegister)
Number MB_I_Reg_12 "Modbus Register 12 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_13 "Modbus Register 13 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_14 "Modbus Register 14 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_15 "Modbus Register 15 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_16 "Modbus Register 16 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_17 "Modbus Register 17 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_18 "Modbus Register 18 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_19 "Modbus Register 19 raw value [%.1f]" (gModbusRegister)
//Number MB_I_Reg_20 "Modbus Register 20 raw value [%.1f]" (gModbusRegister)
// ... usually a Modbus Slave hosts 64 registers
//Number MB_I_Reg_64 "Modbus Register raw value [%.1f]" (gModbusRegister)
// Here we define all coil register which become written by the Modbus Master the number is equal coilnumber
//Number MB_C_Reg_01 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_02 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_03 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_04 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_05 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_06 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_07 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_08 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_09 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_10 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_11 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_12 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_13 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_14 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_15 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_16 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_17 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_18 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_19 "Modbus Register raw value [%.1f]" (gModbusCoils)
//Number MB_C_Reg_20 "Modbus Register raw value [%.1f]" (gModbusCoils)
// ... usually a Modbus Slave hosts 64 registers a 16 coils
//Number MB_C_Reg_1024 "Modbus Register raw value [%.1f]" (gModbusCoils)
You can define the modbus layout of your slave as you want to. You should use for the modbus item fixed names (like MB_I_Reg_01 or MB_C_Reg_01).
By doing this your nodejs script keeps working in an abstract way.
By implementing a rule like the following
rule "MB_I_Reg_01" when Item MB_I_Reg_01 changed then HzgAussenTemp.sendCommand ((MB_I_Reg_01.state as DecimalType) / 10) end
rule "MB_I_Reg_02" when Item MB_I_Reg_02 changed then HzgVorlaufTemp.sendCommand ((MB_I_Reg_02.state as DecimalType) / 10) end
rule "MB_I_Reg_03" when Item MB_I_Reg_03 changed then HzgRuecklaufTemp.sendCommand ((MB_I_Reg_03.state as DecimalType) / 10) end
rule "MB_I_Reg_04" when Item MB_I_Reg_04 changed then HzgSollVorlaufTemp.sendCommand ((MB_I_Reg_04.state as DecimalType) / 10) end
rule "MB_I_Reg_05" when Item MB_I_Reg_05 changed then SP_Ladeleitung.sendCommand ((MB_I_Reg_05.state as DecimalType) / 10) end
rule "MB_I_Reg_06" when Item MB_I_Reg_06 changed then SP_Kuppel.sendCommand ((MB_I_Reg_06.state as DecimalType) / 10) end
rule "MB_I_Reg_07" when Item MB_I_Reg_07 changed then SP_Kopf.sendCommand ((MB_I_Reg_07.state as DecimalType) / 10) end
rule "MB_I_Reg_08" when Item MB_I_Reg_08 changed then SP_Reserve1.sendCommand ((MB_I_Reg_08.state as DecimalType) / 10) end
rule "MB_I_Reg_09" when Item MB_I_Reg_09 changed then SP_Reserve2.sendCommand ((MB_I_Reg_09.state as DecimalType) / 10) end
rule "MB_I_Reg_10" when Item MB_I_Reg_10 changed then SpitzbodenRT.sendCommand ((MB_I_Reg_10.state as DecimalType) / 10) end
rule "MB_I_Reg_11" when Item MB_I_Reg_11 changed then RT_WQ1.sendCommand ((MB_I_Reg_11.state as DecimalType) / 10) end
rule "MB_I_Reg_12" when Item MB_I_Reg_12 changed then BT_WQ1.sendCommand ((MB_I_Reg_12.state as DecimalType) / 10) end
you link the abstract modbus item to your desired Item. Here you also can recalculated the value. In Modbus systems you normaly do not use “real” values. Instead of that you multiplicate the real value by 10 or 100 send the result as integer and at the target you have to divide it again, to get the “real” value back.
there the rules file is the best aproach.
7. Step
Now you can check if its working by watching your karaf console.
8. Step
To get the system run again after reboot you have to add the node call to the crontab
@reboot /opt/node/bin/node /home/pi/projekte/cmi_modbus/CMI_Modbus.js
Alternative you create the call as systemd service which is probably the better way. But I have now idea about the howto at the moment. But I’am open for your comments
PS: I’am sorry for the formatting, but sometimes it is not working as it should. But I can not find the error. So some steps in bold are not working, when they are followed by a code fence. The same to the uploaded picture.