Bitwise Operations in a Domain-Specific Language
I have been working with several devices controlled by a programmable logic controller (PLC). The PLC shows the state of these devices as an unsigned 16-bit integer. The integer value itself isn't very helpful and can even be a bit confusing. Each of the 16 bits reflect whether the device is in a specific state. For example, if the device is a heater and bit 16 is on (1), it signals an alarm. If bit 2 is on (1), it may indicate that the heater is active.
My aim was to read those 16 bits for each device from the PLC using a domain-specific language as part of a wider piece of legacy software. Once I was able to read-in the value as an integer, I had to find a way to parse the information so that I could determine the state of each individual bit.
As someone who has rarely used bitwise operations, I probably would have come up with a solution like this (if using Python) that doesn’t use bitwise operations at all. It solves the issue with string operations.
def is_bit_on(data, bit) -> bool:
# Convert to binary, pad, and get the bit of interest
return bin(data)[2:].zfill(16)[bit] == "1"
# Example: 32768 (1000000000000000 in binary)
print(is_bit_on(32768, 0)) # Output: True
print(is_bit_on(32768, 1)) # Output: False
Bitwise operations were not really on my radar, but there was certainly no way to do complex string operations. Delving in to the documentation I could see that the options I had available to me to process the integer were:
IF(<condition>, <expr1>, <expr2>)
BITAND(<int1>, <int2>,…, <intn>)
BITOR(<int1>, <int2>,…, <intn>)
BITXOR(<int1>, <int2>,…, <intn>)
BITLSHIFT(<int1>, <int2>)
BITRSHIFT(<int1>, <int2>)
A wild idea would be to list all the integers where each bit of interest is on and check for them in an IF statement. For example, if you are interested in the most significant bit (as we were), this would be all numbers from 32,768 to 65,535. This is clearly impractical, and the length of expressions (like the IF statement) is limited to 2048 characters in this proprietary language. We need another way forward.
Admittedly I did not come to the solution by myself. After doing some research into other devices and how they might do this already, one had the answer. After much staring I now understand how this neat little bit of code works to give us what we need.
ATTRIBUTE logical heaterAlarm
BEGIN
Definition IF(BITAND([<uint16_value>],0x8000) > 0, 1, 0)
END
In the example above:
An attribute of type logical is declared. An attribute of this type can be 1 or 0.
The value of this variable is then defined as the result of an IF expression; one of our limited set of options. The result of this expression must be 1 or 0 for it to be stored in an attribute of type logical.
The BITAND (bitwise AND) operation, when applied to two numbers, returns 1 for each bit if both bits are 1; otherwise, it returns 0.
0×8000 is the hexadecimal representation of the binary number
1000 0000 0000 0000. It is used here as a mask to isolate the most significant bit. If you were interested in, for example, the second most significant bit you would use the mask 0×4000 (0100 0000 0000 0000).The value could be any unsigned 16-bit integer. Lets assume it is 32,769 or
1000 0000 0000 0001.The result of the BITAND operation will be 32,768
1000 0000 0000 0000and we have now isolated the bit of interest. The integer after this operation can only be 32,768 or 0.We get our required 1 or 0 using the IF expression. If the result of the BITAND operation is greater than zero, then we know our bit of interest was on. Otherwise it was not.
This approach effectively solves the problem and shows the power of bitwise operations. It has deepened my understanding and appreciation for their use in programming. I believe this experience also emphasises the importance of research. It's likely that someone else has faced the same problem and already solved it. You can spend a lot of time trying to create an original solution, or you can invest some time finding out how it has already been done.




