Support for Zwave binding enhancement request - COMMAND_CLASS_SCHEDULE v1

I have opened an issue on github about the possibility of adding partial support for the SCHEDULE command class.
The issue is described below and can be found at: github.com/openhab/org.openhab.binding.zwave/issues/1553
If anyone else is interested in this or has suggestions / comments, please add a comment.


Would it be possible to provide a “raw” implementation for Command_Class_Schedule v1?
I’m imagining that the Thing has a Channel (e.g. “raw_schedule”) that can be used for transferring a raw Payload between a linked text/string Item and the physical device…

This partial implementation of the class in the binding would then allow interested users to get / set whatever they needed (probably via rules and other items) without the binding developers having to worry about how to integrate with the core UI.

Perhaps a bit messy, but some implementation has to be better than no implementation!

Has been mentioned on the forum before (e.g. Danfoss Devolo Room Sensor: COMMAND_CLASS_SCHEDULE V1 unknown command 5)

My specific use-case is that I have a Secure SCS-317 thermostat that reports the current setpoint via this Schedule class (command 5) and without support for this class I am unable to use openHAB to monitor target setpoint changes that occur as a result of any physical interaction with the device (or due to the schedule). I am not happy with idea of leaving my heating system under open as opposed to closed control…!

OH version: 3.0.1
Binding: 3.0.1

I am sure a pull request with the solution would be welcomed. The developer of this binding & Zigbee is very busy and does this in his spare time. I suspect more important real-life things are currently occupying his time.

As I said on the GH issue, this sort of feature isn’t really something I would look to do, and IMHO it’s not a good idea. It’s not something that 99% of people will use, although I fully appreciate in the “home hacker” market there are always a few people who want to bit bash stuff.

This ability to send low level commands directly onto the bus with no checking etc is highly prone to error and abuse and could cause problems with peoples configurations, so IMHO it’s a bad idea - even if I appreciate that it would be welcomes by a very small group of users.

1 Like

I appreciate your position on this.
If the main objection is placing unchecked data on the bus, then how would you feel about this being a read-only feature?

An earlier model of the SCS-317 used to report setpoints via TARGET_SETPOINT_HEATING and worked very well with openHAB. I suspect more devices will migrate to reporting via the SCHEDULE class.

The inability to read reports from this device would be a deal-breaker for me.
I’m not a power user nor a programmer, but am motivated to learn if necessary!
Which source files would need to be modified if I were to try implementing this as a read-only feature myself? As far as I can tell I would need to write a converter and modify the command class implementation (@ org.openhab.binding.zwave/ZWaveScheduleCommandClass.java at main · openhab/org.openhab.binding.zwave · GitHub ).

I’m sure there’s a fair bit I’m overlooking in my ignorance!

Regardless of outcome, I’d like to thank you for your herculean efforts creating and maintaining this binding! You’ve been a great help to the community (both github and forum contributions).

Making it read-only would alleviate the potential to send erroneous data - fine - but I’m still not sure I understand the need here.

If there is functionality that is needed (eg processing the schedule commands) then why not add it properly rather than provide a means to read raw messages, and then process them in a script? I suspect you’ll (rightly!) tell me that this is more work, and possibly (also rightly!) that it may not be easy to fit in these commands into one of the OH types conceptually, but if we can find a way to do that, it would be a lot better than providing raw methods to read data as it benefits more people (ie a lot of people don’t want to bit bash the low level protocols - and they shouldn’t have to).

Thanks for the discussion :+1:

1 Like

You’re welcome - I enjoy a good discussion! :slightly_smiling_face:
Reading through the Z-wave specification has made it clear to me just how much work this would be and that it is a difficult (if not impossible) task to fit this in to existing OH types. Please consider this request closed!
If I can think of a way that might work without polluting the binding with incomplete implementations I’ll be sure to run it by you. You never know what the future may hold…

On a positive note (for me), I have managed to get the thermostat to update the setpoint on manual button presses. This means I can continue to use openHAB for home automation! :clap:
It’s definitely a bodge, but it works.
The code is awful (I’m not a programmer. Suggestions for changes/improvements welcome). It’s fragile as it depends on the DEBUG output of the z-wave binding remaining unchanged.

If anyone else wants / needs this capability:
This involved editing the logging configuration to spit out just the zwave packet that corresponds to a schedule override report (identified using the Z-wave Log Viewer) into a seperate log.
The LogReader binding was then set to monitor this log file, with a Custom Pattern specified as .Receive Message =.

UID: logreader:reader:tstatlog1
label: ThermostatLogReader
thingTypeUID: logreader:reader
configuration:
  customPatterns: .*Receive Message =.*
  refreshRate: 60000
  warningPatterns: WARN+
  errorPatterns: ERROR+
  filePath: ${OPENHAB_LOGDIR}/thermostat.log

An item was linked to the lastCustomEvent channel of the LogReader Thing to contain the relevant string from the log file.

A rule was then created to parse the log entry to isolate the byte corresponding to the Temperature and convert it from hex into decimal. Finally, events.postUpdate was called with the Thermostat_Setpointheating item and the new value to ensure the UI state corresponded to the real state.

triggers:
  - id: "3"
    configuration:
      itemName: ThermostatLogReader_LastCustomEvent
    type: core.ItemStateUpdateTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        //var logger =
        Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.model.script.Rules');
        var ReportText;
        var ReportSubstring;
        var NewTemp;

        //logger.info("Message for logging in openhab.log");
        ReportText = itemRegistry.getItem('ThermostatLogReader_LastCustomEvent').getState().toString();
        ReportSubstring = ReportText.slice(ReportText.length - 6, ReportText.length - 3);
        NewTemp = parseInt(ReportSubstring, 16)/10;
        events.postUpdate('Thermostat_Setpointheating', NewTemp.toString() + "°C");
    type: script.ScriptAction

Would you mind posting the logging config?
I guess zwave binding needs to be in DEBUG mode, right?

Sure (Caveats apply: this is specific to my setup, so your mileage may vary):
I have an additional Appender defined as:

		<!-- Zwave Thermostat DEBUG custom file appender -->
		<RollingRandomAccessFile fileName="${sys:openhab.logdir}/thermostat.log" filePattern="${sys:openhab.logdir}/thermostat.log.%i" name="THERMOSTAT">
			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5.5p] [%-36.36c] - %m%n"/>
			<RegexFilter onMatch="ACCEPT" onMismatch="DENY" regex=".*Receive Message = 01 1B 00 04 00 07 15 53 05 FF 00 FF 30 00 00 7F 3F 00 01 00 01 06 43 01 01 22 00.*"/>
			<Policies>
				<OnStartupTriggeringPolicy/>
				<SizeBasedTriggeringPolicy size="2 MB"/>
			</Policies>
		</RollingRandomAccessFile>

This Appender writes to the specified logfile ONLY when a line matches the RegexFilter. The filter is set to match payloads corresponding to Override Schedule Reports sent by my thermostat. The string was determined using the Zwave viewer linked to previously and importantly is only the initial part of the payload (as the setpoint will obviously vary between Override Reports). Successful matches are written to the specified logfile. It currently does NOT match other Schedule Reports (i.e. Regular Schedule Reports that are sent on normal schedule transitions) - I’m still working on that bit.

EDIT: In my case, a modified RegexFilter of:
.*Receive Message = 01 1B 00 04 00 07 15 53 05 .. 00 FF 30 00 .. .. .. 00 .. 00 01 06 43 01 01 22 00.*
enables the capture of both manual button presses (Override schedules) and automatic transitions (Regular schedules). My thermostat also sends Regular Schedule Reports after receiving Setpoint Set commands from openHAB, so I do get “double updates” to the linked item which do not cause me any issues.
End EDIT

I then have the ZWAVE Logger defined as:

		<!-- Zwave custom logger with added appender for thermostat packet logging -->
		<Logger additivity="false" level="DEBUG" name="org.openhab.binding.zwave">
			<AppenderRef ref="ZWAVE" level="INFO"/>
			<AppenderRef ref="THERMOSTAT" level="DEBUG"/>
		</Logger>

This is responsible for spitting DEBUG level output to the THERMOSTAT Appender, with INFO level output going to the ZWAVE Appender. This SHOULD still allow me to change the output level for the ZWAVE log by editing <AppenderRef ref="ZWAVE" level="INFO"/> to INFO, DEBUG, TRACE, whatever. The way this is done here might break your ability to change Logging Levels via the Karaf Console (I don’t know and can’t test as I am unable to login to the console of my openhab Docker container)…

Hope this helps.
As always, comments / suggestions welcome!

Thank you, @John.1 , you’re help is much appreciated!
I have 5 Danfoss DRS21 wall thermostats, but until now, the scheduler was disabled.
I applied your detailed explanation for logging the received zwave message and right now I’m toying with one of the thermostats…
Here’s my config (shameful copy-paste from yours :laughing:):

<!-- Zwave Thermostat DEBUG custom file appender -->
		<RollingRandomAccessFile fileName="${sys:openhab.logdir}/thermostat.log" filePattern="${sys:openhab.logdir}/thermostat.log.%i" name="THERMOSTAT">
			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5.5p] [%-36.36c] - %m%n"/>
			<RegexFilter onMatch="ACCEPT" onMismatch="DENY" regex=".*Receive Message = 01 1D 00 04 00 61 15 53 05 01 00 FF .. 00 00 3F 3F.*"/>
			<Policies>
				<OnStartupTriggeringPolicy/>
				<SizeBasedTriggeringPolicy size="2 MB"/>
			</Policies>
		</RollingRandomAccessFile>

And with this, I manage to catch the messages form node 97:

2021-03-09 09:26:04.946 [DEBUG] [WaveSerialHandler$ZWaveReceiveThread] - Receive Message = 01 1D 00 04 00 61 15 53 05 01 00 FF 30 00 00 3F 3F 00 01 00 01 06 43 01 01 22 00 E1 AA 00 26 
2021-03-09 09:34:41.780 [DEBUG] [WaveSerialHandler$ZWaveReceiveThread] - Receive Message = 01 1D 00 04 00 61 15 53 05 01 00 FF 20 00 00 3F 3F 00 00 00 01 06 43 01 01 22 00 E1 A2 00 3F 
2021-03-09 09:40:45.359 [DEBUG] [WaveSerialHandler$ZWaveReceiveThread] - Receive Message = 01 1D 00 04 00 61 15 53 05 01 00 FF 30 00 00 3F 3F 00 14 00 01 06 43 01 01 22 00 E6 B1 00 2F 
2021-03-09 09:45:12.539 [DEBUG] [WaveSerialHandler$ZWaveReceiveThread] - Receive Message = 01 1D 00 04 00 61 15 53 05 01 00 FF 30 00 00 3F 3F 00 07 00 01 06 43 01 01 22 00 E6 AE 00 23 
2021-03-09 09:48:27.725 [DEBUG] [WaveSerialHandler$ZWaveReceiveThread] - Receive Message = 01 1D 00 04 00 61 15 53 05 01 00 FF 30 00 00 3F 3F 00 03 00 01 06 43 01 01 22 00 E1 B0 00 3E 

Logreader setup and started testing with some rule:

rule "LogReader_ThermostatLog_LastCustomEvent"
when
	Item LogReader_ThermostatLog_LastCustomEvent changed
then
	val received_command = LogReader_ThermostatLog_LastCustomEvent.state.toString.split("=").last
	val schedule = Integer::parseInt(transform("JS", "hex2dec.js", received_command.substring(55,57)))
	val temp = Integer::parseInt(transform("JS", "hex2dec.js", received_command.substring(79, 84).replace(" ",""))) / 10.0
	logError("DEBUG", "schedule = [" + schedule.toString + "]")
	logError("DEBUG", "temp = [" + temp.toString + "]")
end

and here’s the output:


==> /var/log/openhab/openhab.log <==

2021-03-09 09:48:28.318 [ERROR] [org.openhab.core.model.script.DEBUG ] - schedule = [3]

2021-03-09 09:48:28.319 [ERROR] [org.openhab.core.model.script.DEBUG ] - temp = [22.5]

Next step, of course, implement some timers…
After successful testing node97 I’ll need to modify the regex pattern of the logger to catch all schedule command for the 5 thermostat nodes - need to dig deeper to properly understand the zwave message structure and fields. Right now, it’s some nebula there :roll_eyes:

One again, many-many thanks!

Oh, and “many-many thanks” to @codegrau and his post. From his post I got the schedule and setpoint temperature!

Glad it worked for you!

Hopefully I can save you some “digging” time:

I’m pretty sure that your thermostat nodeIDs are shown in the 6th position of the message (61 hex → 97 decimal).

For command class message structure (or “payload”), I found the specification helpful.

The full message in the DEBUG log lines seems to start with 4 bytes before the message payload and end with a checksum byte according to what I found in the binding code:

  • A ZWave serial message frame is made up as follows
  • Byte 0 : SOF (Start of Frame) 0x01
  • Byte 1 : Length of frame - number of bytes to follow
  • Byte 2 : Request (0x00) or Response (0x01)
  • Byte 3 : Message Class (see SerialMessageClass)
  • Byte 4+: Message Class data >> Message Payload
  • Byte x : Last byte is checksum

Thanks @John.1 , I got that much :smiley: and I already tinkering further…
The regex filter is now modified to 'catch1 the other 5 nodes:

<RegexFilter onMatch="ACCEPT" onMismatch="DENY" regex=".*Receive Message = 01 1D 00 04 00 .. 15 53 05 01 00 FF .. 00 00 3F 3F.*"/>

And here’s my updated rule - I need to get the node and the item that it’s linked to the heating setpoint of that node:

rule "LogReader_ThermostatLog_LastCustomEvent"
when
	Item LogReader_ThermostatLog_LastCustomEvent changed
	or
	Item rule_test1 received command ON
then
	val received_command = LogReader_ThermostatLog_LastCustomEvent.state.toString.split("=").last
	val node = transform("JS", "hex2dec.js", received_command.substring(16,18))
	val schedule = Integer::parseInt(transform("JS", "hex2dec.js", received_command.substring(55,57)))
	val temp = Integer::parseInt(transform("JS", "hex2dec.js", received_command.substring(79, 84).replace(" ",""))) / 10.0

	logError("DEBUG", "node = [" + node.toString + "]")
	logError("DEBUG", "schedule = [" + schedule.toString + "]")
	logError("DEBUG", "temp = [" + temp.toString + "]")

	val item = transform("JSONPATH", "$.channels[1].linkedItems[0]", sendHttpGetRequest("http://XXXXXXX:XXXXXXXX@localhost:8080/rest/things/zwave%3Adevice%3A1628a3e05cc%3Anode" + node))
	logError("DEBUG", "item = [" + item.toString + "]")
end
2021-03-09 12:06:25.977 [ERROR] [org.openhab.core.model.script.DEBUG ] - node = [116]
2021-03-09 12:06:25.978 [ERROR] [org.openhab.core.model.script.DEBUG ] - schedule = [1]
2021-03-09 12:06:25.979 [ERROR] [org.openhab.core.model.script.DEBUG ] - temp = [23.5]
2021-03-09 12:06:26.206 [ERROR] [org.openhab.core.model.script.DEBUG ] - item = [Radu_Heating_TargetTemp]

Everything is working in the right direction! :laughing:

1 Like