Modbus binding: resend values to device after power down

Hello,

I’ve got some issues getting the combination of my Wago PLC 750-881 working stable via modbus.
When everything stays online, everything works perfect. The problem arises when my PLC is powered off.
When OpenHab server keeps running and I turn off my PLC, the modbus coils are not set again in the PLC when the PLC is running again. (they are ON in OpenHab, but not TRUE again in PLC).

I’m running openHAB 2.4.0 Release Build with the Modbus Binding on Ubuntu.
Below my config:

modbus.things:

//Declaratie van WAGO PLC (Modbus slave) op IP=192.168.1.150
 Bridge modbus:tcp:wago [ host="192.168.1.150", port=502, id=1 ] {

    //Binnenlezen van alle PLC outputs.
    //Deze worden op %MW500-M599=99*16 = 1584 outputs gezet
    //Modbus startadres voor bits: MX0.0 start op modbus adres 12288.
    //500 woorden * 16 bits => +8000 ==> Dus modbus startadres: 20288
    Bridge poller wagooutputsstat [ start=20288, length=1024, refresh=100, type="coil" ] {
        Thing data mb_stat_output0 [ readStart="20288", readValueType="bit"]
        Thing data mb_stat_output1 [ readStart="20289", readValueType="bit"]
        Thing data mb_stat_output2 [ readStart="20290", readValueType="bit"]
        Thing data mb_stat_output3 [ readStart="20291", readValueType="bit"]
        Thing data mb_stat_output4 [ readStart="20292", readValueType="bit"]
        Thing data mb_stat_output5 [ readStart="20293", readValueType="bit"]
        Thing data mb_stat_output6 [ readStart="20294", readValueType="bit"]
        Thing data mb_stat_output7 [ readStart="20295", readValueType="bit"]
    }

    //Controlebits om vanuit OpenHab de PLC aan te sturen
    //Deze staan op MW600-M699 = 99*16=1584 controle bits
    //Modbus startadres voor bits: MX0.0 start op modbus adres 12288.
    //600 woorden * 16 bits => +9600 ==> Dus modbus startadres: 21888
    Bridge poller wagooutputscon [ start=21888, length=1024, refresh=100, type="coil" ] {
        Thing data mb_con_bit_0 [ writeStart="21888", writeValueType="bit", writeType="coil"]
        Thing data mb_con_bit_1 [ writeStart="21889", writeValueType="bit", writeType="coil"]
        Thing data mb_con_bit_2 [ writeStart="21890", writeValueType="bit", writeType="coil"]
        Thing data mb_con_bit_3 [ writeStart="21891", writeValueType="bit", writeType="coil"]
        Thing data mb_con_bit_4 [ writeStart="21892", writeValueType="bit", writeType="coil"]
        Thing data mb_con_bit_5 [ writeStart="21893", writeValueType="bit", writeType="coil"]
        Thing data mb_con_bit_6 [ writeStart="21894", writeValueType="bit", writeType="coil"]
        Thing data mb_con_bit_7 [ writeStart="21895", writeValueType="bit", writeType="coil"]
    }
}

modbus.items:

Switch mb_con_bit_0 "mb_con_bit_0" {channel="modbus:data:wago:wagooutputscon:mb_con_bit_0:switch", autopudate="false"}
Switch mb_con_bit_1 "mb_con_bit_1" {channel="modbus:data:wago:wagooutputscon:mb_con_bit_1:switch", autopudate="false"}
Switch mb_con_bit_2 "mb_con_bit_2" {channel="modbus:data:wago:wagooutputscon:mb_con_bit_2:switch", autopudate="false"}
Switch mb_con_bit_3 "mb_con_bit_3" {channel="modbus:data:wago:wagooutputscon:mb_con_bit_3:switch", autopudate="false"}

Variable declaration in Wago PLC:

	(*Digital control via modbus (%MW600-M699 = 99*16=1584 controle bits)		*)
	(*#Modbus startadres voor bits: MX0.0 start op modbus adres 12288.			*)
	(*600 woorden * 16 bits => +9600 ==> Dus modbus startadres: 21888			*)
	(*Used to change state of outputs											*)
	mb_con_bit_0 		AT %MX600.0: BOOL := FALSE;		(*Adres 21888*)
	mb_con_bit_1 		AT %MX600.1: BOOL := FALSE;		(*Adres 21889*)
	mb_con_bit_2 		AT %MX600.2: BOOL := FALSE;		(*Adres 21890*)
	mb_con_bit_3 		AT %MX600.3: BOOL := FALSE;		(*Adres 21891*)
	mb_con_bit_4 		AT %MX600.4: BOOL := FALSE;		(*Adres 21892*)
	mb_con_bit_5 		AT %MX600.5: BOOL := FALSE;		(*Adres 21893*)
	mb_con_bit_6 		AT %MX600.6: BOOL := FALSE;		(*Adres 21894*)
	mb_con_bit_7 		AT %MX600.7: BOOL := FALSE;		(*Adres 21895*)

Code in Wago PLC:

(*Uitgangen Leefruimte*)
uitgang0:=mb_con_bit_0;
uitgang1:=mb_con_bit_1;
uitgang2:=mb_con_bit_2;
uitgang3:=mb_con_bit_3;

Example:
PLC + OpenHab both up and running.
I set mb_con_bit_1 to ON via OpenHab.
In PLC: mb_con_bit_1 becomes TRUE --> uitgang0 switches to TRUE --> My light turns on.
Now I turn off the PLC.
–> Light turns off ofcourse.
In OpenHab: mb_con_bit_1 is still ON.
When I now turn on PLC again, mb_con_bit_1 bit in PLC doesn’t become TRUE again.
When i set mb_con_bit_1 to OFF in OpenHab and back to ON, the bit becomes TRUE again in the PLC.

Does anyone know the solution to this? I want my lights to remain in the same state after a power cycle.

When I switch on via the sitemap, this is the log:

2019-05-12 10:34:15.820 [ome.event.ItemCommandEvent] - Item ‘mb_con_bit_1’ received command ON
2019-05-12 10:34:15.837 [nt.ItemStatePredictedEvent] - mb_con_bit_1 predicted to become ON
2019-05-12 10:34:15.844 [vent.ItemStateChangedEvent] - mb_con_bit_1 changed from OFF to ON

Does it act more like a “MOMENTARY SET command” instead of "switch this boolean to ON?

Really need to get this stable if I want to deploy this in my house.

Thanks already!

Robbe

Kind of. Only commands get transmitted over Modbus. Commands are usually triggered by UI actions or generated by rules. Commands are a transient event.

There is no built-in ability to decide that some external device doesn’t match what openHAB thought it was, and transmit a command to correct it.
But of course you can use rules to create your own version of something like that.

A requirement to do that would be the ability to read back the external device’s state. I can see some problems for you here -

That message only comes from autoupdate (in response to a command).
You don’t generally want autoupdate with read-polled Modbus Items, and you have tried to select autoupdate=“false” so I really wouldn’t expect to see that.
The problem here is that you have a typo

Switch mb_con_bit_3 "mb_con_bit_3" {channel="modbus:data:wago:wagooutputscon:mb_con_bit_3:switch", autopudate="false"}

autoupdate= not autopudate=

Once that’s sorted, your next issue is that you have defined data Things mb_con_bit_0 and friends as write only.
You don’t seem to have any means to read the PLC…

Is this what the mb_stat_output0 data Things are about? If so, you can combine your read and write into one data Thing.
The read part of a data Thing must correspond with the poller Thing it belongs to, of course.
But the write part can be some other register address altogether, even a different type (this is why they are specified separately).

Get your Items reading properly and we’ll think about your error recovery. This might help with ideas (detecting device coming online after failure)

1 Like

Thinking about this, it’s an interesting challenge.

Things we need to take account of;
We can detect a failed PLC in more than one way.
We can take special rule-based recovery actions.
A normally configured read-write Modbus Item will preserve the last read state indefinitely (unless we do something like expire it).
That sounds useful here … but … when a failed device comes back online the first read poll will overwrite the preserved data.

I think if you want some kind of restore-on-recovery utility, you are going to need some separate data store to the read polled data.

There’s more than one way to go about this.

A really quick and dirty way is to set up what you already accidentally had; write-only openHAB Items, with autoupdate true.
These Items would track UI or rules triggered commands, and pass to PLC.
The restore part would require a rule triggered from device coming online, and simple read the Item state and resend the same command to itself.
It’s a dirty method because openHAB Items will NOT track any changes the PLC might make e.g. timers or wallswitches etc.

Still thinking about restore-slave-after-fault :smiley:

I’d go about it like this.

Configure Modbus Items in the normal read-write way (both openHAB and PLC initiated changes will update the Items)
Put the Items wanted to recover in a group gMBrestore or whatever.

I have mapdb persistence, for use with restore on startup for a few Items. I do not use it for Modbus Items - pointless, because they get read polled anyway during startup.
But we can exploit it as an on-demand data store, as well. Only stores one value per Item, and that’s all we need.

Set up a rule to trigger from the Modbus poller bridge Thing changing status.

If it goes offline (because a read poll failed), we iterate through our group of Items and call persist on each one.
Optionally, we could also set the Item’s states to UNDEF for use in rules or UI display. Probably best to do that after some delay.

If it goes online (which would only happen after a failure), we iterate through the group, fetch the previousState from persistence, and use that to send a new command to the Item (which will also go out on Modbus)

A limitation is that if openHAB changes something in the meantime - say, a light turned on at dusk - it will get missed. More complexity needed to deal with that.

Thanks alot for all of your ideas and thoughts! I’m thinking this through before writing any code.
I’ll share my thoughts below.

In the Wago PLC I set the outputs via the modbus bits “mb_con_bit_x”.
I copy the actual state of the output to “mb_stat_bit_x”. So I always know if an output is actually set or not. This is a good thing, now I need to figure out how to make good use of this.

By the way: I can set the mb_con_bits to RETAIN in the PLC. This way they keep the value when the power is cycled. If modbus turned them ON before the PLC is powered down, those bits will still be TRUE in the PLC after it is turned on again.
This might be the easiest way to solve this… But we still have a problem when a user keeps trying to turn something on while the PLC is offline.
(This might be part of a solution that could work for me, however it wouldn’t be generic for all modbus devices…)

This would probably be a good solution. I need to read your great topic about modbus error management more in detail to write something to do this.
Could you explain to me what autoupdate=true/false exactly means? I don’t know what it does.
If I change it to autoupdate=true, will the poller write the coils again and again and again without any user/rule interaction?

Here you have indeed the issue that changes to the mb_con_bit aren’t tracked. I think the previous idea would be better.
Just resend the status (openhab status) of mb_con_bit to the PLC.
I probably first have to turn off the bit before i send the ON command again. I’ll try it out.

Do you agree that the second idea (resending the actual openhab status to PLC) is the best solution?

Would there be any way to program this in the binding? To keep all the config files clean.
Not sure if I would even be able to do this, only got some classes on Java, no real coding experience with Java.

Is there any better solution to get everything working 100% perfect and stable?
This seems to be a lot of trouble to get it working stable.
Don’t have any experience with MQTT or http requests, but it might be better to use these?
Both can be used on Codesys V2.3.

I tried to detect the slave going offline, but couldn’t get it working…
Your example:

Rule "modbus thing fault"
when
   Thing "modbus:poller:slave22:s22_coils" changed
then
   var Tstatus = getThingStatusInfo("modbus:poller:slave22:s22_coils").getStatus()
   if (Tstatus.toString == "OFFLINE") {
      sendMail([you@email.net](mailto:you@email.net), "Fault report", "Modbus slave22 offline")
   }
end

I tried like this, but a change to oflfine doesn’t get detected.:
My modbus thing:

 Bridge modbus:tcp:wago [ host="192.168.1.150", port=502, id=1 ] {
    Bridge poller wagooutputscon [ start=21888, length=1024, refresh=100, type="coil" ] {
        Thing data mb_con_bit_0 [ writeStart="21888", writeValueType="bit", writeType="coil"]
    }
}

My rule to detect the above slave going offline (with all diferent quotes):

rule "modbus thing fault1"
when
   Thing "modbus:poller:wago:wagooutputscon" changed
then
   /* 
   var Tstatus = getThingStatusInfo("modbus:poller:wagooutputscon:mb_con_bit_0").getStatus()
   if (Tstatus.toString == "OFFLINE") {
      logInfo("Notification", "MODBUS SLAVE NU OFFLINE")
   }
   */
   logInfo("Notification", "1111111111111111111111111111111111111")
end

rule "modbus thing fault2"
when
   Thing 'modbus:poller:wago:wagooutputscon' changed
then
   logInfo("Notification", "2222222222222222222222222222")
end

rule "modbus thing fault3"
when
   Thing modbus:poller:wago:wagooutputscon changed
then
   logInfo("Notification", "3333333333333333333333333333333333")
end

However the log said it changed:

2019-05-13 11:23:11.606 [hingStatusInfoChangedEvent] - ‘modbus:poller:wago:wagooutputscon’ changed from ONLINE to OFFLINE (COMMUNICATION_ERROR): Error with read: org.openhab.io.transport.modbus.ModbusConnectionException: Error connecting to endpoint ModbusTCPSlaveEndpoint@43abb4ac[address=192.168.1.150,port=502]

But no notification from those rules…

Any idea what I’m doing wrong?

Yes, of course. It’s especially relevant to both write-only and frequently-polled devices.

An idealized openHAB Item, linked to a real device, will receive commands. Commands get passed out via binding. Some time later, the device responds with a new status. The binding updates the Item state, closing a command->state (or cause->effect) loop.

In real life, devices may be very slow to respond, or not respond at all (write only), or an Item may not even be linked to a binding/device. This makes a crappy UI experience amongst other issues. Click something, expect a response.

Autoupdate feature tackles that - it is essentially a psuedo-binding listening for commands to each Item. It has a guess about what the result may be (this can be influenced by any actual linked binding) and applies its prediction to update the Item. (n.b. NOT instant command->response, but very nearly)
If a real response comes along later, that’s fine, the Item still gets a state update in the usual way.

The speed-up behaviour is considered useful enough, it’s enabled by default. It may optionally be disabled for individual Items.

For a promptly responding device, or a frequently read-polled device, you probably do not really want autoupdate (because you can get weird race conditions between the prediction and the real status).
I’d disable it for frequently polled Modbus Items - want to know the real state please, not the educated guess.

In contrast, for a write-only device, you would almost certainly want to leave autoupdate enabled.

I’d resist that. Many people won’t want unexpected start ups after some already rare and unexpected event. I’m assuming that because no-one has yet beaten this autorecover path …

Yes, I should have elaborated. It is buried in that error management guide. Your poller bridge Thing is write-only, there are no read data Things. No read failure, no poller->offline.

You can detect write errors in a different way, or detect read errors on another poller; but I don’t really understand why you are resisting having a combined read/write poller.since you seem to have a readback of outputs available.

Thanks for the explanation about autoupdate, clear now!

I’ve tried a read-write poller, when I set mb_con_bit_0 to ON, it becomes ON in the PLC.
When I switch off the PLC, OpenHab sets mb_con_bit_0 to OFF automatically. So only this isn’t the solution.
With this rule I asked about, I want to detect the slave becoming ONLINE again, and then resend all the values to the mb_con_bits.
But I’ll try again with a read poller now.

I think I found the solution. First quick tests seem to work.

rule "Wago is back online"
when
   Thing "modbus:poller:wago:wagoinputs" changed
then

   var Tstatus = getThingStatusInfo("modbus:poller:wago:wagoinputs").getStatus()
   if (Tstatus.toString == "ONLINE") {
      logInfo("Notification", "WAGO PLC (MODBUS) IS NOW BACK ONLINE")

      logInfo("Notification", "Current state mb_con_bit_0: " + mb_con_bit_0.state)

      sendCommand(mb_con_bit_0, mb_con_bit_0.state)
   }
end

This sends the state that openhab knows to the PLC when PLC comes back online.
Even if I switch the output in OpenHab, it just sends the “OpenHab state” to the PLC.

For me it is OK like that.

autoupdate=false for the control bits (write coils).

I’ll test a bit more and try to write some clean code. I’ll probably put all the modbus control bits in a group and do something with that. I’ll post my solution later.

Thanks for your help rossko57!

There are a few personal preferences at work here, but all can be tweaked to suit.
I wouldn’t want the openHAB end appearing to be ON while the PLC is actually broken, for example :wink:

This should not happen. The Modbus binding does not update Items belonging to failed read channels (it’s up to us to decide what to do with them e.g. expire).
Maybe your PLC does something in its death throes, which gets read by openHAB.
Maybe it’s some unexpected twist on a write-only channel.

For clarity, I understood you have a coil like mb_con_bit_0 for writing and a corresponding read-back bit like mb_stat_output0
These can be combined like so -


// the poller describes ONLY the range to read
 Bridge poller wagooutputsstat [ start=20288, length=8, refresh=100, type="coil" ] {
// the data Thing has a read path included in the poller range
// but each write path is described independently
     Thing data mb_stat_combo0 [ readStart="20288", readValueType="bit", writeStart="21888", writeValueType="bit", writeType="coil"]

I wouldn’t read poll 1024 bits either, mind you !

When I use a read/write combo (tested with the same address), when the PLC went offline, the status in OpenHab changed to OFF.
I’m sure I’ll get it all working as it should eventually.
But I’m limited in time right now to write and test the proper solution.

For anyone who needs this, here is my solution.
This solution does 3 things:
1) If OpenHab stays up, PLC goes down and comes back up after a few seconds: OpenHab rule will resend the status of the modbus bits.
2) If PLC stays up but OpenHab goes down: OpenHab will resend the status of the modbus bits when OpenHab is started again AND if the PLC is online. Otherwise it waits with resending the values.
3) Both PLC and OpenHab go down and come back online: Rule 2 applies. Usually PLC is already back online before OpenHab is and OpenHab will resend values. Otherwise it waits until the PLC is also online.

modbus.things:

//Declaratie of WAGO PLC (Modbus slave) on IP=192.168.1.150
 Bridge modbus:tcp:wago [ host="192.168.1.150", port=502, id=1 ] {

    //Below is a poller which just reads 1 testbit from WAGO PLC
    //Tries frequently (50ms) and only ONCE.
    //This is to quickly detect if PLC is offline.
    Bridge poller wagoquickpoll [ start=13888, length=1, refresh=50, maxTries=1, type="coil" ] {
        Thing data quickpolltestbit [ readStart="13888", readValueType="bit"]
    }
}

modbus.items:

//All modbus control bits which have to be restored need to be put in this group.
//They will receive a new command when the PLC comes online again.
Group g_ModbusWagoOutputs

//WAGO Control Bits:
Switch mb_con_bit_0 "mb_con_bit_0" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_0:switch", autoupdate="true"}
Switch mb_con_bit_1 "mb_con_bit_1" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_1:switch", autoupdate="true"}
Switch mb_con_bit_2 "mb_con_bit_2" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_2:switch", autoupdate="true"}
Switch mb_con_bit_3 "mb_con_bit_3" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_3:switch", autoupdate="true"}
Switch mb_con_bit_4 "mb_con_bit_4" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_4:switch", autoupdate="true"}
Switch mb_con_bit_5 "mb_con_bit_5" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_5:switch", autoupdate="true"}
Switch mb_con_bit_6 "mb_con_bit_6" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_6:switch", autoupdate="true"}
Switch mb_con_bit_7 "mb_con_bit_7" (g_ModbusWagoOutputs) {channel="modbus:data:wago:wagooutputscon:mb_con_bit_7:switch", autoupdate="true"}

modbuserrorhandler.rules:

//Below rule is just for extra info in the logger.
rule "WAGO PLC CHANGED TO OFFLINE"
when
   Thing "modbus:poller:wago:wagoquickpoll" changed
then
   var Tstatus = getThingStatusInfo("modbus:poller:wago:wagoquickpoll").getStatus()
   if (Tstatus.toString == "OFFLINE") {
      logInfo("WAGO PLC CHANGED TO OFFLINE", "MODBUS PLC NOW OFFLINE")
   }
end

//Rule to resend values when PLC comes online again
rule "WAGO PLC CHANGED TO ONLINE"
when
   Thing "modbus:poller:wago:wagoquickpoll" changed
then

//Determine the new PLC status: online/offline:
var Tstatus = getThingStatusInfo("modbus:poller:wago:wagoquickpoll").getStatus()
   if (Tstatus.toString == "ONLINE") {
      logInfo("Notification", "WAGO PLC IS NOW BACK ONLINE.")
      logInfo("Notification", "All wago control bits will be sent again.")

      //Loop through all members of wago outputs. If their state is not NULL, then resend the command.
      g_ModbusWagoOutputs.members.forEach[item |
      logInfo("Notification", "Current state of item: " + item + ":" + item.state)
      if (item.state!=NULL){
         sendCommand(item, item.state)
         }
      ]
   }
end

startup.rules:

rule "Initialize modbus control bits after openhab started"
when
System started
then
//Wait until WAGO PLC/Modbus Poller comes back online
var Tstatus = getThingStatusInfo("modbus:poller:wago:wagoquickpoll").getStatus()
logInfo("Notification", "OPENHAB IS IS RESTARTED!")
while(Tstatus.toString != "ONLINE"){
    Thread::sleep(500)
    Tstatus = getThingStatusInfo("modbus:poller:wago:wagoquickpoll").getStatus()
    logInfo("Notification", "WAIT 500MS. STATE OF PLC: " + Tstatus.toString)
}

logInfo("Notification", "WAGO PLC STATE IS NOW: " + Tstatus.toString)

//After WAGO PLC is back online, resend all control bits
g_ModbusWagoOutputs.members.forEach[item |
logInfo("Notification", "HUIDIGE STATUS item: " + item + " :" + item.state)
if (item.state!=NULL){
    sendCommand(item, item.state)
    }
]
end

Only downside:
If the PLC is powercycled in like 1 or 2 seconds, the values will be lost in the PLC and OpenHab doesn’t detect that the PLC went offline. So the values will not be resent.

To solve this, I’ve set the modbus control bits in the WAGO PLC to RETAIN:

VAR_GLOBAL RETAIN
(Digital control via modbus (%MW600-M699 = 9916=1584 controle bits) )
(
#Modbus startadres voor bits: MX0.0 start op modbus adres 12288. *)
(*600 woorden * 16 bits => +9600 ==> Dus modbus startadres: 21888 *)
(*Used to change state of outputs *)
mb_con_bit_0 AT %MX600.0: BOOL := FALSE; (Adres 21888)
mb_con_bit_1 AT %MX600.1: BOOL := FALSE; (Adres 21889)
mb_con_bit_2 AT %MX600.2: BOOL := FALSE; (Adres 21890)
mb_con_bit_3 AT %MX600.3: BOOL := FALSE; (Adres 21891)
mb_con_bit_4 AT %MX600.4: BOOL := FALSE; (Adres 21892)
mb_con_bit_5 AT %MX600.5: BOOL := FALSE; (Adres 21893)
mb_con_bit_6 AT %MX600.6: BOOL := FALSE; (Adres 21894)
mb_con_bit_7 AT %MX600.7: BOOL := FALSE; (Adres 21895)

mb_con_bit_8 AT %MX600.8: BOOL := FALSE; (Adres 21896)
mb_con_bit_9 AT %MX600.9: BOOL := FALSE; (Adres 21897)
mb_con_bit_10 AT %MX600.10: BOOL := FALSE; (Adres 21898)
mb_con_bit_11 AT %MX600.11: BOOL := FALSE; (Adres 21899)
mb_con_bit_12 AT %MX600.12: BOOL := FALSE; (Adres 21900)
mb_con_bit_13 AT %MX600.13: BOOL := FALSE; (Adres 21901)
mb_con_bit_14 AT %MX600.14: BOOL := FALSE; (Adres 21902)
mb_con_bit_15 AT %MX600.15: BOOL := FALSE; (Adres 21903)
END_VAR

If the PLC is turned off, the values of those bits are still remembered. When the PLC is turned on again. First thing it does is reset the correct values.
If in the meantime you would have turned of some bits via OpenHab, they would still be turned off in the PLC.

So for me this works perfectly as my goal was to recover properly after a powerloss.

1 Like