Issue with single precision not converting (analog output / present value)

New SSD in and system restored, thanks to Time Machine!

I took that log output, boiled it down to just what is needed, reordered chronologically, and regrouped all open tests and all closed tests together. Here’s what that looks like:

Log Output

Open via App Tests

22:49 Sending open command 
22:49 Received command to open 
22:50 Open command confirmed  
22:59 C8 42 00 F023 0000 0000  (17096)

23:59 Sending open command 
23:59 Received command to open 
23:59 Open command confirmed 
23:59 00 00 00 F023 00C0 3903 
24:08 C8 42 00 F023 0000 0000  (17096)

24:20 Sending open command 
24:20 Received command to open

24:52 Sending open command 
24:52 Received command to open 
24:53 Open command confirmed 
25:00 90 41 00 F023 0000 0000  (16784)
25:02 C8 42 00 F023 0000 0000  (17096)

25:15 Sending open command 
25:15 Received command to open 
25:24 refreshing

25:51 Sending open command 
25:52 Received command to open 
25:52 Open command confirmed 
25:52 00 00 00 F023 00C0 3903 
26:00 F8 41 00 F023 00C0 3903  (16888)
26:02 C8 42 00 F023 0000 0000  (17096)

26:34 Sending open command 
26:34 Received command to open 
26:34 00 00 00 F023 00C0 3903 
26:35 Open command confirmed 
26:44 C8 42 00 F023 0000 0000  (17096)

Manual Open Tests

41:34 E0 40 00 F023 00C0 3903  (16608)
41:40 60 41 00 F023 00C0 3903  (16736)
41:42 C8 42 00 F023 0000 0000  (17096)

44:20 A0 40 00 F023 00C0 3903  (16544)
44:20 A0 40 00 F023 00C0 3903  (16544)
44:28 C8 42 00 F023 0000 0000  (17096)

45:43 E0 40 00 F023 00C0 3903  (16608)
45:50 0C 42 00 F023 00C0 3903  (16908)
45:51 C8 42 00 F023 0000 0000  (17096)

47:14 60 41 00 F023 00C0 3903  (16736)
47:20 E0 41 00 F023 00C0 3903  (16608) ***
47:21 C8 42 00 F023 0000 0000  (17096)

48:28 A0 40 00 F023 00C0 3903  (16544)
48:35 04 42 00 F023 00C0 3903  (16900)
48:36 C8 42 00 F023 0000 0000  (17096)

Close via App Tests

21:25 Sending close command
21:25 Received command to close 
21:26 Close command confirmed
21:35 00 00 00 F023 0000 0000

23:32 Sending close command 
23:32 Received command to close 
23:32 C8 42 00 F023 00C0 3903  (17096)
23:33 Close command confirmed 
23:40 94 42 00 F023 00C0 3903  (17044)
23:42 00 00 00 F023 0000 0000

24:27 Sending close command
24:27 Received command to close 
24:27 C8 42 00 F023 00C0 3903  (17096)
24:28 Close command confirmed 
24:35 92 42 00 F023 00C0 3903  (17042)
24:35 92 42 00 F023 00C0 3903  (17042)
24:37 00 00 00 F023 0000 0000

25:31 Sending close command 
25:32 Received command to close 
25:32 Close command confirmed 
25:32 C8 42 00 F023 00C0 3903  (17096)
25:40 86 42 00 F023 00C0 3903  (17030)
25:41 00 00 00 F023 0000 0000

26:09 Sending close command 
26:10 Close command confirmed 
26:10 Received command to close 
26:10 C8 42 00 F023 00C0 3903  (17096) 
26:19 00 00 00 F023 0000 0000

Manual Close Tests

39:12 BC 42 00 F023 00C0 3903  (17084)
39:20 64 42 00 F023 00C0 3903  (16996)
39:21 00 00 00 F023 0000 0000

43:40 BC 42 00 F023 00C0 3903  (17084)
43:48 00 00 00 F023 0000 0000

44:54 BA 42 00 F023 00C0 3903  (17082)
45:00 AC 42 00 F023 00C0 3903  (17068)
45:02 00 00 00 F023 0000 0000

46:29 BC 42 00 F023 00C0 3903  (17084)
46:35 A0 42 00 F023 00C0 3903  (16544)
46:35 Open command confirmed
46:35 Open command confirmed
46:37 00 00 00 F023 0000 0000
46:43 Automatically set state to open after 8 seconds

47:52 BC 42 00 F023 00C0 3903  (17084)
48:00 00 00 00 F023 0000 0000

New observations:

  • In the open/close via app test, it appears there was one instance of the automatic countdown timer not working.
  • I have no idea why an “open command confirmed” (which occurs when an on/off: 0 Zigbee message is received from the motor) happened after you started a manual close. There may be a way to add a trap in the code to prevent that. I’ll have to give some thought as to how to implement it.
  • A value of 0000000000F02300000000 is sent when the motor is finished closing the curtain. I think it’s safe to use that in the code to change the state from closing to closed, but keeping the automatic countdown timer as a fallback method.
  • A value of 0000C84200F02300000000 is sent when the motor is finished opening, but since bytes 3 & 4 might relate to position and could be different for other users, I think a condition on the final 4 bytes being 00000000 and the first 5 bytes being something besides 0000000000 should work as a method to change the state from opening to open, again with the automatic countdown timer as a fallback method.
  • Now I’m seeing byte #4 values of 40, 41, and 42 during the manual open / close tests, and it seems possible that it could be linked to byte #3, but as big-endian value. So I converted all of those 16-bit integer values to look for any patterns (I put those values in parenthesis). In all tests except one manual open run, the 16-bit value goes down on close and goes up on open. That one exception (marked with ***) seems to blow the theory out of the water, and looking at both the native hex values and the corresponding 16-bit integers, I don’t see any patterns that would make those values of any practical use (for example, giving a percentage opened / closed update).

So, at this point it seems there’s a fairly reliable way for the device handler to “know” when the motor has finished opening/closing the curtain, which works both for app-based and manual actions. However, because of uncertainty surrounding those bytes #3 & #4, there’s no way for the device handler to “know” that someone has started opening / closing the curtain manually, so in that case you would just see the state change from open to closed when the manual closing is finished (or vice-versa).

So, I have updated the DTH code to handle the curtain finished opening / closed messages, which should work both when done manually or through the app. The only limitation is that if the curtain is opened / closed manually, there will be no log messages or events until it is finished. Hopefully that is good enough! If it is opened / closed via the app, the countdown is still there as a fallback to send an open or closed event, but I increased the time to 15 seconds (to allow time for users with longer rails). The countdown time length could be made adjustable by adding a preference setting.

I also added “traps” in the code to handle redundant open / close requests from the app. They will be ignored (with a log message to that effect) unless the current state is opposite of the request.

The updated code can still be copied from the same link as before, here.

Detailed Change List
• (Hopefully) fixed info log message output of specific bytes of interest in any read attribute messages from Cluster 000D / Attribute ID 0055
• Modified parseReportAttributeMessage() to handle motor finished opening/closing messages
• Removed some unnecessary log output messages
• Added traps in open() and close() to ignore redundant open/close requests
• Changed automatic countdown to call motorfinished() routine to 15 seconds
• Added trap in motorfinished() to prevent redundant door open or door closed events
• Fixed log output in refresh() to use displayInfoLog()
• Renamed motorFinished() to motorFinishedCountdown()
• Added additional helpful comments in the code

So someone has released what appears to be a fully working DTH for the Aqara Curtain Motor:

I guess I missed it, but this DTH uses the Window Shade capability instead of Door Control.

Also, the command to set a partial open / close “level” is in there, and it’s sent to cluster 000D / attribute ID 0055. I asked the author how s/he figured that one out, because I was not able to find any documentation on the Zigbee commands despite a lot of searching.

I’d be very curious as to whether it changes the state from open → partly open → closed (and vice-versa) correctly.

Either way, that DTH is a lot more developed that what I’ve started, so you may want to work with that author in testing.

It looks like they only posted this 8 hours ago.
As you asked on the other thread, I’m really curious how they figured it out.
So far it seems to work well. Open/close both work, as does partial open/close and the app updates based on manual events.
Thanks for letting me know about this!

So I’ve dug into that DTH code from @ShinJjang and can now answer your original question properly!

It turns out Xiaomi is using Cluster 000D / Attribute 0055 correctly, but there’s some extra data in the read attr message’s value hex string that SmartThings provides to the DTH which isn’t parsed and labelled without using the proper zigbee parse function call.

Here’s what happens, using one example (using the app to close) from your log output you posted last week:

  1. The SmartThings parses a read attribute message from the motor, and because it’s from a cluster that isn’t commonly used, it’s passed on as this “raw” message to the device handler:

read attr - raw: 39C001000D1C5500390000A04200F02300C03903, dni: 39C0, endpoint: 01, cluster: 000D, size: 1C, attrId: 0055, encoding: 39, value: 0339c00023f00042a00000

  1. The value portion of that output can only be further parsed by using the ST documented function call zigbee.parseDescriptionAsMap(description) which gives us this:

raw:39C001000D1C5500390000604100F02300C03903, dni:39C0, endpoint:01, cluster:000D, size:1C, attrId:0055, encoding:39, value:41600000, additionalAttrs:[[attrId:f000, attrInt:61440, encoding:23, value:0339c000, isValidForDataType:true, consumedBytes:14]], isValidForDataType:true, clusterInt:13, attrInt:85

  1. Now we can see there are “additional attributes” embedded in the value hex string, and the 32-bit binary value is revealed as 41600000. I’m not sure why the encoding is listed as 23 (Unsigned 32-bit int) inside that additionalAttrs section, but the main encoding of 39 is correct as it corresponds to Single Precision Float (as seen in ST’s list of Data Types here.)

  2. Next that hex value has to be converted to a single precision float. I have only been programming in Groovy since last November with no prior Java experience, and only to improve device handlers for Xiaomi devices, so I am not well versed in Java classes and objects vs primitives. So there may be different ways to do the conversion, but here’s what that DTH does:

  • Convert the value string to a Long value (essentially an unsigned 32-bit integer):
    long convertedValue = Long.parseLong(parseMap["value"], 16)
    which in this example give us the decimal 1096810496. I have to guess that Long is used because the value is too large to convert to an integer.

  • Convert the value into Single Precision Float (IEEE754 Single precision 32-bit) by mapping the binary bits of the value using Float.intBitsToFloat(int):
    Float percentage = Float.intBitsToFloat(convertedValue.intValue())

  1. And now you’ve got the percentage, which in this example is 14.0%

As far as setting the percentage level open/close of the motor goes, a reverse conversion is needed, and the resulting 32-bit binary hex string is sent as the value of a zigbee.writeAttribute command to the same cluster / attribute (000D/0055) as the read attribute messages are sent from.

So, I am sorry for barking up the wrong tree with that value hex string, and of course having dove into it for a while, it all totally makes sense now!

Wow. That is far more complex than I thought it would be when I first started looking into this. Didn’t even know you could embed attributes like that.

Thank you so much for all of your help with this.

Out of curiosity, do you mind if I ask what got you interested in improving DTH for Xiaomi devices, even ones you don’t use?

No problem, it’s been a good learning process.

I got interested in helping to improve the device handlers for Xiaomi devices because I bought some of their door/window sensors, and found there was some aspects of the DTH that could be improved. Then @ArstenA started a GitHub repository of new and improved DTHs for Xiaomi devices, building on work done by @a4refillpad (and a bunch of other people). They were simple enough that I thought it would be a good way to reintroduce myself to coding (the last time I did any was BASIC + Machine Code on an Apple //c in the 80s.)

I now have about 30 Xiaomi devices, and gained quite a bit of understanding about the basics of DTHs for Zigbee devices, and also for Xiaomi devices in particular, so I’ve tried to help build / improve them even for devices I don’t have because they are great for people trying to do Home Automation budget (though admittedly not without some pitfalls and downsides), and I know how frustrating it can be buying a device and it doesn’t work correctly because of an existing device handler or the lack of one altogether.

1 Like

That’s cool. I really appreciate your help. Buying Xiaomi saved me a considerable amount of money on this project.