Convert hexadecimal string to a number (Part 1)
Although Loxone supports parsing hexadecimal strings on input (RS232/485, virtual HTTP input command, ...)
there are apparently situations when this doesn't work. One of these cases is Jablotron alarm replying to an explicit request for peripheral status (motion sensors) via RS485. The answer is a 16 bytes bitmap formatted as 32 hexadecimal characters. It is either too much for Loxone to handle or it works only for 4 lower bytes (last 8 characters), not sure.
SIDENOTE: Strangely, Jablotron sends the status update also automatically, every 8 seconds. This update contains only 16 characters (8 bytes), which Loxone seems to support and parse with \h (although I haven't tested this fully so I don't know if all 8 bytes are supported). If you don't want to wait 8 seconds and want to trigger it on your own, keep reading.
This article will help you to decode any two hexadecimal characters on your own
Interpret them as \2\1:
That will give you 16-bit (big-endian word) representation of two ASCII characters. For "2A" (42 dec) on input you get 12865, because "2" is coded as 50 in ASCII, "A" as 65 in ASCII, "2" is the more significant byte => 50 * 256 + 65 = 12865. We want 42, though.
Let's use the Formula function block. Unfortunately it is very limited, so it's getting ugly. We don't have any logic expressions, bit-wise operators, conditions. All you have is (as per Loxone help/doc):
Formula
The 4 inputes are referenced to as: I1, I2, I3, I4
The following operands are available: +,-,*,/,^
The following functions are available: PI, ABS, SQRT, SINH, COSH, TANH, ARCTAN, LN, LOG, EXP, SIN, COS, TAN, ARCSIN, ARCCOS, INT, RAD, DEG, SIGN
Example: (I1+(I2*0.005)/sin(I3)
We need to separate higher and lower byte, convert them from ASCII HEX to a digit (0-15) and recreate the original byte.
Separate lower and higher bytes (ie. retrieve ASCII values for both characters separately) - integer value of division by 256, integer value of modulo 256
Convert ASCII values (48='0' 49='1', ... 58='9' and then 65='A', 66='B', ... 70='F') to 0..15. Subtract 48 for numbers <=58, otherwise subtract 55.
RESULT = first * 16 + second
Since we don't have conditional expressions, how do we subtract 48 for one value, 55 for the other? Let's use multiplication by 0 / 1:
(X - 48) * FLAG + (X - 55) * (1 - FLAG)
If FLAG is 0, then 55 is subtracted, 48 if FLAG is 1.
Now we need to find a way transforming the input to the flag, ie. 0 for 48..58 and 1 for 65..70. Oh look, a logarithm!
INPUT | ASCII | ASCII-48 | LOG10(ASCII-48) | LOG10((ASCII-48)*0.9+1) | INT(LOG10((ASCII-48)*0.9+1) |
---|---|---|---|---|---|
'0' | 48 | 0 | n/a | 0,00 | 0 |
'1' | 49 | 1 | 0,00 | 0,28 | 0 |
'2' | 50 | 2 | 0,30 | 0,45 | 0 |
'3' | 51 | 3 | 0,48 | 0,57 | 0 |
'4' | 51 | 4 | 0,60 | 0,66 | 0 |
'5' | 52 | 5 | 0,70 | 0,74 | 0 |
'6' | 54 | 6 | 0,78 | 0,81 | 0 |
'7' | 55 | 7 | 0,85 | 0,86 | 0 |
'8' | 56 | 8 | 0,90 | 0,91 | 0 |
'9' | 57 | 9 | 0,95 | 0,96 | 0 |
'A' | 65 | 17 | 1,23 | 1,21 | 1 |
'B' | 66 | 18 | 1,26 | 1,24 | 1 |
'C' | 67 | 19 | 1,28 | 1,26 | 1 |
'D' | 68 | 20 | 1,30 | 1,28 | 1 |
'E' | 69 | 21 | 1,32 | 1,30 | 1 |
'F' | 70 | 22 | 1,34 | 1,32 | 1 |
Let's put it together:
Readable implementation:
Formula on the left - just:
int(I1 / 256)
Both "flag" formulas in the middle of the diagram:
int(log((I1 - 48) * 0,9 + 1))
The resulting Formula on the right:
((I3 - 55) * I4 + (I3 - 48) * (1 - I4)) * 16 + ((I1 - 55) * I2 + (I1 - 48) * (1 - I2))
Single function block implementation:
Perfect from outside:
Unreadable from inside
((int(I1 / 256) - 55) * int(log((int(I1 / 256) - 48) * 0,9 + 1)) + (int(I1 / 256) - 48) * (1 - int(log((int(I1 / 256) - 48) * 0,9 + 1)))) * 16 + ((I1 - int(I1 / 256) * 256 - 55) * int(log((I1 - int(I1 / 256) * 256 - 48) * 0,9 + 1 )) + (I1 - int(I1 / 256) * 256 - 48) * (1 - int(log((I1 - int(I1 / 256) * 256 - 48) * 0,9 + 1))))
Yet, it is the resulting formula from previous scheme with expanded I1, I2, I3, I4.
Just cut'n paste this formula, that's it.
Enjoy,
 Aleš
Part 2 of Convert hexadecimal string to a number:
Convert hexadecimal string to a number (Part 2)