Converting int16 to two characters

Hello community members.

I’m facing a challange converting an unsigned int16 value received from a modbus item containing the decimal representation of two ASCII codes for two characters into it’s two characters into readable string item.

Here’s my setup:

Thing file:

//Modbus message parser: https://rapidscada.net/modbus/
Bridge modbus:serial:SPH10000 "Modbus SPH10000" [ port="/dev/ttyUSB0", id=1, baud=9600, stopBits="1.0", parity="none", dataBits=8, encoding="rtu", timeBetweenTransactionsMillis=350 ]
{
	Bridge poller growattGeneralSettings "Allgemeine Einstellungen" [start=15, length=1, type="holding", refresh=60000,maxTries=2]  //start=608, length=1, refresh=12000, type="holding", maxTries=2 ] 
	{
		Thing data growattLcdLanguage "LCD Sprache" [ readStart="15", readValueType="uint16"]
	}
	Bridge poller growattSerial "Seriennummer" [start=23, length=5, type="holding", refresh=60000,maxTries=2]  //start=608, length=1, refresh=12000, type="holding", maxTries=2 ] 
	{
		Thing data growattSerial1 "Seriennummer1" [ readStart="27", readValueType="uint16"]
	}

Item file:

Number	growattSerial1	 "Serial# 1st digit"  {channel="modbus:data:SPH10000:growattSerial:growattSerial1:number"}

The value in item growattSerial1 is 17223

(as to my knowledge, 17223 should represent string CG,
17223 = binary: 0 1 0 0 0 0 1 1 0 1 0 0 0 1 1 1
byte 1:
0 1 0 0 0 0 1 1 => 67 = ASCII code for C
byte 2:
0 1 0 0 0 1 1 1 => 71 = ASCII code for G)

How can I transform or convert the number Item (with value 17223) into a string item (with value CG) ??

Any help is highly appreciated!

Thanks and kind regards,
Ralph…

Found a way to achive my objective but it appears awfully complicated:

Things file (one modbus data item for each of the five uint16 registers containing the values for the 5 serial number characters):

Bridge modbus:serial:SPH10000 "Modbus SPH10000" [ port="/dev/ttyUSB0", id=1, baud=9600, stopBits="1.0", parity="none", dataBits=8, encoding="rtu", timeBetweenTransactionsMillis=350 ]
{
	Bridge poller growattSerial "Seriennummer" [start=23, length=5, type="holding", refresh=6000,maxTries=2]  //start=608, length=1, refresh=12000, type="holding", maxTries=2 ] 
	{
		Thing data growattSerial5 "Seriennummer5" [ readStart="23", readValueType="uint16"]
		Thing data growattSerial4 "Seriennummer4" [ readStart="24", readValueType="uint16"]
		Thing data growattSerial3 "Seriennummer3" [ readStart="25", readValueType="uint16"] 
		Thing data growattSerial2 "Seriennummer2" [ readStart="26", readValueType="uint16"]
		Thing data growattSerial1 "Seriennummer1" [ readStart="27", readValueType="uint16"]	
	} 
}

Items file (one item for each uint16 registers plus a string item which is supposed to contain the serial number in readable text):

Group						gGrowattInverterSerials			"Seriennummern-Items"
Number						growattSerial1					"Serienummer 1. Stelle"							(gGrowattInverter, gGrowattInverterSerials)				{channel="modbus:data:SPH10000:growattSerial:growattSerial1:number"}
Number						growattSerial2					"Serienummer 2. Stelle"							(gGrowattInverter, gGrowattInverterSerials)				{channel="modbus:data:SPH10000:growattSerial:growattSerial4:number"}
Number						growattSerial3					"Serienummer 3. Stelle"							(gGrowattInverter, gGrowattInverterSerials)				{channel="modbus:data:SPH10000:growattSerial:growattSerial3:number"}
Number						growattSerial4					"Serienummer 4. Stelle"							(gGrowattInverter, gGrowattInverterSerials)				{channel="modbus:data:SPH10000:growattSerial:growattSerial2:number"}
Number						growattSerial5					"Serienummer 5. Stelle"							(gGrowattInverter, gGrowattInverterSerials)				{channel="modbus:data:SPH10000:growattSerial:growattSerial5:number"}
String						growattSerialtxt				"Seriennummer"									(gGrowattInverter)

and a complicated(?) rule to “compute” and concatenate the characters from the high and low bytes of each of the 5 uint16 items:

rule ConcatGrowattSerial
when
	Member of gGrowattInverterSerials changed
	//or Time cron "0 0/1* * * * ? *"
then 
	growattSerialtxt.postUpdate( 	(String::valueOf(Character::toChars( Math::round( (growattSerial5.state as Number).doubleValue / 256).intValue) ) )
	 							 +	(String::valueOf(Character::toChars( ( (growattSerial5.state as DecimalType).intValue() % 256) ) ) )
								 + 	(String::valueOf(Character::toChars( Math::round( (growattSerial2.state as Number).doubleValue / 256).intValue) ) )
	 							 + 	(String::valueOf(Character::toChars( ( (growattSerial2.state as DecimalType).intValue() % 256) ) ) )
								 + 	(String::valueOf(Character::toChars( Math::round( (growattSerial3.state as Number).doubleValue / 256).intValue) ) )
	 							 + 	(String::valueOf(Character::toChars( ( (growattSerial3.state as DecimalType).intValue() % 256) ) ) )
								 + 	(String::valueOf(Character::toChars( Math::round( (growattSerial4.state as Number).doubleValue / 256).intValue) ) )
	 							 + 	(String::valueOf(Character::toChars( ( (growattSerial4.state as DecimalType).intValue() % 256) ) ) )
								 +	(String::valueOf(Character::toChars( Math::round( (growattSerial1.state as Number).doubleValue / 256).intValue) ) )
	 							 + 	(String::valueOf(Character::toChars( ( (growattSerial1.state as DecimalType).intValue() % 256) ) ) )							 							 
							   )
end

Is that the way it should be or is there an easier way of getting the serial number from the five uint16 modbus registers?

Binary manipulation in Java is usually pretty awkward and Rules DSL is just using the underlying Java classes. There are other ways to achieve this using binary shift operators and the like but they will pretty much all be equally as awkward.

This works, I’d just add some comments and move on.

The only minor improvement I can think of might be to move it to a SCRIPT transformation using Rules DSL as the language (so you don’t have to figure out how to do it in another language). Then you can apply the transformation from bits to characters at the Channel or in a Profile instead of in a rule. But you’d still need the rule to concatenate the different Items into one so you wouldn’t be saving much.

Hi Rich.

Thanks a lot for the confirmation!
I will take a look at the SCRIPT transformation as yuo’ve suggested.

Kind regards,
Ralph…

There is a feature request to modbus binding tonread sequence of data and return it as a byte sequence (RawType) to permit parsing of custom blocks: [modbus] Raw output from modbus data · Issue #13247 · openhab/openhab-addons · GitHub

Should be quite simple…

val my16BitValue = 17223
val myHigh = my16BitValue >> 8
val myLow = my16BitValue - (myHigh <<8)
logInfo("Result", "myHigh = " + myHigh + " (" + Character.toString(myHigh) + ")")
logInfo("Result", "myLow = " + myLow + " (" + Character.toString(myLow) + ")")

I had as similar problem with the RFID reader from my wallbox. The RFID can have up to 16 signs but modbus can only deliver 32 bit (= 4 bytes) per register. So I have to concatenate the RFID each time someone authorizes for a charging session. In your case it should work like this:

var String growattSerial = growattSerialtxt.state
var highByte = (triggeringItem.state as Number).intValue >> 8
var lowByte = (triggeringItem.state as Number).intValue - (highByte << 8)

switch(triggeringItem.name.substring(13,14)) {
  case "1": { growattSerial = growattSerial.substring(0, 8) + Character.toString(highByte) + Character.toString(lowByte) }
  case "2": { growattSerial = growattSerial.substring(0, 2) + Character.toString(highByte) + Character.toString(lowByte) + growattSerial(4) }
  case "3": { growattSerial = growattSerial.substring(0, 4) + Character.toString(highByte) + Character.toString(lowByte) + growattSerial(6) }
  case "4": { growattSerial = growattSerial.substring(0, 6) + Character.toString(highByte) + Character.toString(lowByte) + growattSerial(8) }
  case "5": { growattSerial = Character.toString(highByte) + Character.toString(lowByte) + growattSerial.substring(2) }
}

growattSerialtxt.postUpdate(growattSerial)

Careful because you have your group members as triggers. If you don’t use reentrant locking you might mess up your serial.

Not in OH 3+ and even in OH 2 not in languages other than Rules DSL. Each rule gets exactly one thread to operate in. If the rule triggers while it’s already running, the triggers are queued up and worked off in sequence. There is pretty much no reason to use a reentrant lock in OH 3 unless you are doing something weird with Timers. And even then, in some languages like JS Scripting, the Timers are made thread safe too.

Don’t want to argue with you about that. I remember that I had to use reentrant locking in OH 2.5 for my RFID reader on the wallbox because I wasn’t able to get the whole RFID from four 32-bit modbus registers by relying on thread safety. After using a reentrant lock my authorization check worked as I expected.
After having found the solution for my problem I didn’t dig deeper. So maybe there is a solution without rentrant lock…

Yes, in 2.x- versions of OH with Rules DSL any give rule could run any number of times, even at the same time. And when the same rule is running twice the second rule can interfere with the first rule currently running.

But even then reentrant locks were dangerous because even with a try/catch/finally there was no guarantee that the finally will always be run therefore the lock could get stuck locked forever. There was also the problem with the fact that there was only 5 threads total, shared by all rules.

In the Experimental Rules Engine in 2.5 (which became just the rules engine in OH 3) instead of sharing five threads among all rules, each rule gets their very own thread. But that means any give rule can only have one trigger running at a time and subsequent triggers queue up.

The reentrant lock comes for free now and it applies to all rules.

Good to know. Maybe I’ll try to remove the reentrant lock from my rule and see if it still works.

Slightly OT, but my rule now works without explicitly using reentrant lock.