How to split HEX string parameter into bits with a PVM code plugin?

Extracting specific bits of a HEX string and converting into integer.

Some trackers send custom bytes of information that can’t be parsed for the whole protocol and is stored as a hexadecimal string parameter.

For example, Teltonika Manual CAN elements are stored as indexed parameters can.data.frame.X. If you need to extract certain bytes from such parameters, the PVM code plugin may help you with this. Here’s how to do it.

Here is a message that among others contains the can.data.frame.11 parameter — a hexadecimal string that carries 8 bytes of information:

{
  "battery.current": 0,
  "battery.level": 100,
  "battery.voltage": 4.129,
  "can.data.frame.11": "47544B0100014AE1",
  "din": 0,
  "din.1": false,
  "din.2": false,
  "engine.ignition.status": false,
  "event.priority.enum": 0,
  "external.powersource.voltage": 48.601,
  "ident": "555444333111222",
  "movement.status": false,
  "position.altitude": 567,
  "position.direction": 344,
  "position.hdop": 0.6,
  "position.latitude": 17.833562,
  "position.longitude": 87.149355,
  "position.pdop": 1.2,
  "position.satellites": 15,
  "position.speed": 0,
  "position.valid": true,
  "server.timestamp": 1649922838.082161,
  "timestamp": 1649922834
}

PVM plugin configuration

To arrange custom parsing of the can.data.frame.11 parameter, we need a “msg-pvm-code” plugin with the following script:

pvm plugin configuration

Here’s the PVM code in plain text in case you decide to copy it:

optional .can.data.frame.11 ==> %hex ==> bits:
8 ==> skip    
16 ==> #bms.capacity.remaining                            
unset .can.data.frame.11

What does the plugin code do?

If the can.data.frame.11 parameter is present in the original device message (operator optional), the plugin takes the value of this parameter and converts it into an 8-byte integer number, treating the value as a string that describes a hexadecimal number (operator %hex).

After that the plugin extracts certain bits of the resulting integer (operator bits) and does the following:

  • first 8 bits (counting from right to left, i.e. bit 0 - bit 7) — are skipped

  • the next 16 bits (bit 8 - bit 23) — are stored into resulting message as a bms.capacity.remaining parameter

  • and the rest of bits are skipped

Finally, the plugin removes the processed parameter can.data.frame.11 from the device message (operator unset).

Assign the plugin to device(s) (Plugin tab on the Device card) and the code will be applied to every message of the assigned devices. The resulting message will look like this:

{
  "battery.current": 0,
  "battery.level": 100,
  "battery.voltage": 4.129,
  "bms.capacity.remaining": 330,
  "din": 0,
  "din.1": false,
  "din.2": false,
  "engine.ignition.status": false,
  "event.priority.enum": 0,
  "external.powersource.voltage": 48.601,
  "ident": "555444333111222",
  "movement.status": false,
  "position.altitude": 567,
  "position.direction": 344,
  "position.hdop": 0.6,
  "position.latitude": 17.833562,
  "position.longitude": 87.149355,
  "position.pdop": 1.2,
  "position.satellites": 15,
  "position.speed": 0,
  "position.valid": true,
  "server.timestamp": 1649927515.468316,
  "timestamp": 1649922834
}

Here bms.capacity.remaining=330 parameter is a decimal number that caries converted bits 8 - 23 of initial parameter can.data.frame.11.

Skipping empty values

One more thing. If a device can’t read CAN data frame bytes from the CAN reader for some reasons, it may send empty value for can.data.frame.11 parameter:

{
  ...
  "can.data.frame.11": "0000000000000000",
  ...
}

In this case, in order to skip the empty values you may use operator null. The modified code is below:

optional .can.data.frame.11 ==> [null="0000000000000000"] ==> %hex ==> bits:
8 ==> skip    
16 ==> #bms.capacity.remaining                            
unset .can.data.frame.11

In this case the resulting message will not contain "bms.capacity.remaining": 0 parameter with zero value. 


See also
Using plugins to resolve position coordinates into address using Here reverse geocoding API and add it into the device messages.