HowTo: Connect a Modbus Master to OpenHAB via RestAPI (Example tested with an TA CMI Device)

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 :slight_smile:

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.

3 Likes

Great Tutorial, exactly what I need to start integrating the TA CMI with HomeMatic, Fritz!Box and other things. I understood the source and principals of the Modus-Slaveserver.
But as an openHAB-Newbie, I do not yet completely understand how to go further; I need to link the “Item” to a “Thing” first, right? What’s about the bindings?
Sorry, if my questions sound maybe “stupid”(openHAB-Newbie).

Best
Reiner

Hello,

it is not so complicated as you expect.

You only have to get nodejs script to run.

After that you just configure the .item File with the Modbus Register you want to get. Important is the naming of the item. It should be MB_I_Reg_01 and so on. (This is hardcoded in the nodejs script)

Imeadiatly after saving you should see the updating of the item file and a little bit later the first events should arive from the cmi via the script.

As you only become raw integer values (without decimal) you have to caculate the correct value and transmit it to your desired named item. This you do in the .rule file. In my case I devide all integer by 10.

That’s all.

No need for things, no need for bindings.

If not done yet you must install the following addon

(you find it under Addons/Misc) to get the RestAPI to run. This is not a binding, it is a direct entry to your openhab database. Over that you can set or get directly the value of your item. The nodejs script comunicates via that API.

After the installation you will find on your openhab:8080 startpage a new button called “RestAPI”. If you open that you will get an overview of the given possibilities. The script is using at the moment the following call

If you have further question please post more details.

Hi there,

thanks for your quick response!

I have setup a vm with opensuse42 and node.js 6.20.3. I have modified your script, adapting

http://hab:8080/rest/items/MB_I_Reg_${…
to
http://localhost:8080/rest/items/MB_I_Reg_${…

Also i have created 3 files following your instructions:

/etc/openhab/items/MB_I_Reg_01
/etc/openhab/items/MB_I_Reg_01.items(copy)
/etc/openhab/ules/MB_I_Reg_01.rules

The modus_slave server script works, but results into an error as soon as values from the CMI are send:

writing: 1 <Buffer 00 cc>
Status: 404
Body: {
“error”: {
“message”: “Item MB_I_Reg_01 does not exist”
“http code”: 404
}
}

From the karat console I can see that there is communication (but resulting into errors) between the sw-modules already.

Any suggestions?

Again, thanks for your great tutorial and help. Very much appreciated.

Best

Reiner

Did you miss to copy the “.items” or is it realy missing?

And it would be better if you would name yout items File like for example “my_cmi.items”. In that file you should then define only the expected cmi items in the form

Group gModbus           "Modbus"
Group gModbusRegister   "Modbus Register"             (gModbus)

Number MB_I_Reg_01 "Modbus Register 01 raw value [%.1f]"    (gModbusRegister)

In your first step there is no need for an rules file. Your first target is to get the raw values, the second is to convert them.

Instead of that you should create an .sitemap File to show your gModbus group. There you should see than the raw value, and at least your defined modbus item.

If your error keeps, than you can try to set the value of your item manually via the rest-api. If you can change it from there errorfree you will see your value in the sitemap. If you get an error the rest-api will tell you what is wrong.

At the moment I think you have not defined the item correctly.

Indeed, the problem is right now on the Item. It seems, that the items file (now my_cmi.items with your last reduced content) is not loaded by openhab2(lastest snapshot2.1 931).
RestApi item_get result in 404, and I cannot see the item in any UI of openhab.
The Log confirms this problem by … http:/localhost:8080/rest/items/MB_I_Reg_01 does not exist.

I will start investigations later today, but some advice would be very appreciated.

Best
RS

I created the mx_cmi.items from scratch(no copy/paste) and the Item MB_I_Reg_01 is now visible!
I guess the further implementation will work fine now, and I will post the result later today.

Thanks again for your help :wink:

All works great. Thanks a lot.

BTW, have you worked already on implementing fc03(Pushing data to the CMI)?

No, my next step are getting the coils.

But if you want to develop the script further on you can have a look

https://github.com/dresende/node-modbus-tcp

There is the description of the used modbus server.

In our script you have to append the line (last line)

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

modbusServer.on("read-holding-registers", getItemVal)     // checking for event "read-holding-register"     (FC03) holding register is read

The function getItemVal you have to write yourself for the moment.because I have no time for testing at the moment.

The first step is create the function getItemVal.

Within that function you send the rest-api call to get the desired item value by using the put text function.

The return of that call you have to interpret to get the value out and send it back as return to the modbusServer.on event.

If you need help here, I supose you will get a lot of help in the nodeforum.de. Just continue with the thread

http://nodeforum.de/thema/117-wie-modbus-tcp-anwenden/?pageNo=1

I supose it is very easy, but it may take a while to get it running and you may encounter probably timing problems, because I don’t know how long the modbus master will wait for a response.

As you see: Die Tücke steckt im Detail

1 Like