Quick Guide: Decoding VicPack

Other resources you can read about decoding VicPack:

  • VicPack Protocol.pdf
  • Demo source code written in Python: VicPack.py This Python script is for demo purpose only.
  • Demo source code written in Javascript (AMD, module.exports): VicPack.js This Javascript is for demo purpose only.

This document is a quick guide to decode VicPack payload from sensors. VicPack is a simple 5 bytes per package in a payload. Since this is a quick guide, we will skip many details about how VicPack is structured and dive right into how to decode the measurement values.

The payload always starts with 0xFA and ends with 0xCE. Some sensors may also send a 16 bits CRC (checksum) at very ends of the payload (after 0xCE).

This is an example of a payload with CRC (0x39DC) at the end, in hex-string. Space between bytes is to make it easier to read. We will use this payload sample throughout the document.

FA 01 00 00 02 01 04 00 00 01 1A 00 00 23 01 CE 39 DC

Following sample is without CRC:

FA 01 00 00 02 01 04 00 00 01 1A 00 00 23 01 CE

The header always starts with 0xFA. Since VicPack is 5 bytes per package in a payload, the first 5 bytes will be the package for header. The most important byte in this package is the last byte. It describes how many packages you will have in the payload (excluding the header package). Using the sample payload from above, the header package will be:

FA 01 00 00 02

The last byte 0x02 indicate the payload will contain 2 more packages. You can use this information to decide when to stop parsing the payload, or you can stop parsing once you reach 0xCE.

Decode Packages

Splitting the payload into 5 bytes packages, we will get:

FA 01 0B 00 02 Header
01 04 00 00 01 Package 1
1A 00 00 23 01 Package 2
CE End of payload byte

The first byte of each packages describe Measurement Type. The remaining 4 bytes (32 bits) are the raw values for the measurement. The two packages we have in the example will have following measurement types and values

Package 1
01 Measurement Type
04 00 00 01 Raw Values
Package 2
1A Measurement Type
00 00 23 01 Raw Values

In this document, we will not discuss Measurement Type 0x01. You can safely skip that package.

Package 2 has Measurement Type 0x1A, which is a SWITCH_INTERRUPT. This measurement returns two raw values; pin and value. Some measurements are straightforward to decode, some needs formulas to decode. For SWITCH_INTERRUPT, we do some bit shift and few math operations.

Pin: (0x00002301 >> 8) - 17 = 18

Value: 0x00002301 & 0xFF = 1

List of Measurement Types

In this section, we will list most common Measurement Types and how to decode the raw values into human readable values. "rawValue" in this section is the unsigned 4 bytes (32 bites) we going to decode.

Most of the raw values from sensors are in Big-endian (MSB). Some may come as Little-endian (LSB), in this case you need to perform a bit reversal to make the value to MSB. Bit reversion in this section is shown as:

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)

Note: the & 255 is to ensure the byte is signed. In many programming languages, a byte can be signed -127 to +127. The And operator will ensure the byte is unsigned 0 to 255.

 

0x01 - DRIVER_INFO

Byte Description Decode
1 Is Driver enabled. 1 = true, 0 = false Enabled = rawValue & 0x000000FF
2 Driver Index Index = (rawValue & 0x0000FF00) >> 8
3 Driver Slot. Slot = (rawValue & 0x00FF0000) >> 16
4 Driver Type. Type = (rawValue & 0xFF000000) >> 24

 

0x07 - INTERNAL_BATTERY_ON_DIE

Byte Description Decode
1-4

Internal battery on die.

Unit: V (volt)

Endianness: MSB.

Battery = rawValue / 10000.0

 

0x08 - INTERNAL_BATTERY

Byte Description Decode
1-4

Internal battery.

Unit: V (volt)

Endianness: MSB.

Complex formula in Java

final double[] meas = new double[] { 3.32211667225159, 2.80515334784856, 2.40084388357319, 2.29739470604636, 1.8957659397271, 1.59635358098812 };

final double[] real = new double[] { 3.46, 3.1, 2.71, 2.99, 2.8, 2.66 };

double volt = (0.18d / Math.pow(2, 10)) * 2 * rawValue;

double val = 0; double norm = 0;

// loop through measurement, to find the segment

// wich best corresponds to measured voltage boolean fnd = false;

int i = 0;

for (; i < meas.length - 1; i++) {

  if (volt >= meas[i] && volt <= meas[i + 1]) {

    // voltage found, now find normalized value

    norm = (volt - meas[i + 1]) / (meas[i] - meas[i + 1]);

    fnd = true; break;

  }

}

// now use normalized value to obtain real value

if (fnd) {

  val = real[i + 1] - real[i + 1] * norm + real[i] * norm;

} else {

  val = volt;

}

print("Internal Battery On Die = {} V", val);

 

0x0B - INTERNAL_TEMPERATURE

Byte Description Decode
1-4

Internal temperature.

Unit: Celsius °C

All four bytes are used.

Endianness: MSB.

Temperature = rawValue / 100.0

 

0x13 - CHARGE (Waterlevel)

Byte Description Decode
1-4

Capacitance charge value.

Unit: C

Endianness: MSB.

To get charge value, we need to convert raw value to cint16 (16 bits sized, signed integer).

IMPORTANT: due to a firmware bug we need to swap the first and second byte before performing the cint16 conversion.

int b1 = (byte)((rawValue >> 24) & 0xff);
int b2 = (byte)((rawValue >> 16) & 0xff);
int b3 = (byte)((rawValue >> 8) & 0xff);
int b4 = (byte)(rawValue  & 0xff);

int swap2LastBytes = (b1 << 24) | ((b2 & 0xff) << 16) | ((b4 & 0xff) << 8) | (b3 & 0xff);

int val = cint16(swap2LastBytes);

print ("Charge value is {} C", val);

Waterlevel sensor returns 4 charges (values), the orders of each values are:

  • The first value is the charge of the whole sensor.
  • The second value is the charge of the bottom sensor.
  • The third value is for the charge of the middle sensor.
  • The fourth value is for the charge of the top sensor.
     

0x14 - TEMPERATURE

Byte Description Decode
1-4

Temperature.

Unit: Celsius °C

All four bytes are used.

Endianness: MSB.

Use following formula on raw value to convert it to temperature in Celsius.

Temperature = rawValue * 175.72 / 65536 - 46.85

 

0x15 - HUMIDITY

Byte Description Decode
1-4

Humidity.

Unit: % RH

All four bytes are used.

Endianness: MSB.

Use following formula on raw value to convert it to humidity.

Humidity = rawValue * 125/ 65536 - 6

 

0x16 - PRESSURE

Byte Description Decode
1-4

Pressure.

Unit: bar

All four bytes are used.

Endianness: MSB.

No conversion needed.

Pressure = rawValue

 

0x17 - ACCELERATION_X

Byte Description Decode
1-4

Acceleration.

Unit: g

All four bytes are used.

Endianness: MSB.

Acceleration_X = (rawValue >> 6) * 0.0039

 

0x18 - ACCELERATION_Y

Byte Description Decode
1-4

Acceleration.

Unit: g

All four bytes are used.

Endianness: MSB.

Acceleration_Y = (rawValue >> 6) * 0.0039

 

0x19 - ACCELERATION_Z

Byte Description Decode
1-4

Acceleration.

Unit: g

All four bytes are used.

Endianness: MSB.

Acceleration_Z = (rawValue >> 6) * 0.0039

 

0x1A - SWITCH_INTERRUPT

Byte Description Decode
1

A switch / button has been pressed / released.

Unit: -

Endianness: MSB.

Pin, can be use as button ID if more than one button is available. Note that this pin ID may start at 18 or 8431. 

Value:
1 = button pressed
2 = button released.

Pin = (rawValue >> 8) - 17
Value = rawValue & 0xFF

2-4 Unused  

 

0x1B - AUDIO_AVERAGE

Byte Description Decode
1-4

Audio average.

Unit: -

All four bytes are used.

Endianness: MSB.

Audio_Avg = rawValue

 

0x1C - AUDIO_MAX

Byte Description Decode
1-4

Audio max.

Unit: -

All four bytes are used. 

Endianness: MSB.

Audio_Max = rawValue

 

0x1D - AUDIO_SPL

Byte Description Decode
1-4

Audio Peak Detector (VU meter).

Unit: -

All four bytes are used.

Endianness: MSB.

VU = rawValue

 

0x1E - AMBIENT_LIGHT_VISIBLE

Byte Description Decode
1-2

Visible ambient light.

Unit: lux

Endianness: MSB.

Use following formula to decode lux:

exp = rawValue >> 12
mantissa = rawValue & 4095

ambient_light = mantissa * 0.001 * 2exp

3-4 Unused  

0x1F - AMBIENT_LIGHT_IR

Byte Description Decode
1-4

Ambient light IR spectrum.

Unit: lux

All four bytes are used.

Endianness: MSB.

ambient_ir = rawValue

 

0x20 - AMBIENT_LIGHT_UV

Byte Description Decode
1-4

Ambient light UV spectrum.

Unit: index

All four bytes are used.

Endianness: MSB.

ambient_uv = rawValue / 100

 

0x21 - CO2_LEVEL

Byte Description Decode
1-4

CO2 level.

Unit: g (gram)

All four bytes are used.

Endianness: MSB.

co2 = rawValue

 

0x22 - DISTANCE

Byte Description Decode
1-4

Distance.

Unit: mm (millimeters)

All four bytes are used.

Endianness: MSB.

distance = rawValue

 

0x23 - SAMPLE_RATE

Byte Description Decode
1-4

Sample rate.

Unit: ms (milliseconds)

All four bytes are used.

Endianness: MSB.

sample_rate = rawValue

 

0x2B - VOC_IAQ

Byte Description Decode
1-2

The raw value contains VOC IAQ index and VOC state.

Unit: index and state

Only two first bytes are used

Endianness: LSB

To extract index and state, following bits are used:

  • bits 0 to 13 is IAQ index
  • bits 14 to 16 is the state.

 

Click here to read more about IAQ index and state.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)


state = (msb_value >> 14 ) & 3
iaq_index = msb_value & 0x3FFF

3-4 Unused.  

 

0x2C - VOC_TEMPERATURE

Byte Description Decode
1-2

Temperature measured by VOC sensor. 

Unit: Celsius °C

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)
        
temperature = (msb_value & 0xFFFF) / 10.0
 
3-4 Unused.  

 

0x2D - VOC_HUMIDITY

Byte Description Decode
1-2

Humidity measured by VOC sensor.

Unit: % RH

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)
        
humidity = (msb_value & 0xFFFF) / 10.0
3-4 Unused.  

 

0x2E - VOC_PRESSURE

Byte Description Decode
1-2

Pressure measured by VOC sensor.

Unit: Pa

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)
        
pressure = (msb_value & 0xFFFF) * 10.0
2-4 Unused  

 

0x2F - VOC_AMBIENT_LIGHT

Byte Description Decode
1-2

Ambient light measured by VOC sensor.

Unit: Lux

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)
        

exp = msb_value >> 12
mantissa = msb_value & 4095

ambient_light = mantissa * 0.01 * 2exp

2-4 Unused  

0x30 - VOC_SOUND_PEAK

Byte Description Decode
1-2

Audio Peak Detector

Unit: mV

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)

sound_peek = msb_value / 10
        

2-4 Unused  

 

0x31 - TOF_DISTANCE

Byte Description Decode
1-2

Time of Flight

Unit: mm (millimeter) and state (status)

Only two first bytes.

Endianness: LSB.

To extract distance and state, following bits are used:

  • bits 0 to 13 is distance (mm)
  • bits 14 to 16 is the state.

Click here to read more about distance and state/status.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)

tof_state = ( msb_value >> 13 ) & 7
tof_distance = msb_value & 0x1FFF
 

2-4 Unused  

0x32 - ACCELEROMETER_STATUS

Byte Description Decode
1-4

Accelerometer status.

Unit: state

All four bytes are used.

Endianness: MSB.

status = rawValue

 

0x34 - VOLTAGE

Byte Description Decode
1-2

Voltage

Unit: V (Volt)

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)

voltage = msb_value * (3.0 / 216)

 

0x35 - VOLTAGE_DFF

Byte Description Decode
1-2

Voltage Diff

Unit: V (Volt)

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)

voltage_dff = msb_value * (3 / 215)

0x36 - VOLTAGE_REF

Byte Description Decode
1-2

Voltage Reference

Unit: V (Volt)

Only two first bytes.

Endianness: LSB.

msb_value = ((rawValue >> 8) & 255) | ((rawValue & 255) << 8)

voltage_ref = msb_value * (3 / 216)

 

0x37 - FALLING_COUNTER

Byte Description Decode
1-4

Falling edge counter

Unit:

All four bytes are used.

Endianness: LSB.

msb_value = (rawValue  & 255) << 24 | ((rawValue >> 8) & 255) << 16 | ((rawValue >> 16) & 255) << 8 | ((rawValue >> 24) & 255)

counter = msb_value

 

0x38 - RISING_COUNTER

Byte Description Decode
1-4

Rising edge counter

Unit:

All four bytes are used.

Endianness: LSB.

msb_value = (rawValue  & 255) << 24 | ((rawValue >> 8) & 255) << 16 | ((rawValue >> 16) & 255) << 8 | ((rawValue >> 24) & 255)

counter = msb_value

 

0x51 - GPS_DATA

Byte Description Decode
1-4 Streaming decode  

 

0x52 - eCO2 and PIR

Byte Description Decode
1-4

Environmental with eCO2

Decoding environmental  wirh eCO2 requires three package of 0x52.

The first package of 0x52 contains eTVOC and eCO2.

The second package contains eCO2 Error, eCO2 Status and PIR Present and PIR Count.

The third package contains Audio level and Ambient light.

Unit:

  • eTVOC: ppb
  • eCO2: ppm
  • (Reserved) PIR present: 0 = false, 1 = true
  • Sound level: dB(A)
  • Ambient light: lux

All four bytes are used.

Endianness: LSB.

Decode package #1

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

eTVOC = msb_value

msb_value  = (rawValue & 0x000000ff) & 255 << 8 | (rawValue & 0x0000ff00) >> 8) & 255

eCO2 = msb_value  

Decode package #2

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

PIR_Present = (msb_value >> 15)  & 0x01

PIR_Count = msb_value  & 0x7FFF

eCO2_Status = ((rawValue & 0x0000ff00) >> 8) & 255

eCO2_Error = (rawValue & 0x000000ff) & 255

Decode package #3

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

Audio_Level =  msb_value / 10.0

msb_value  = (rawValue & 0x000000ff) & 255 << 8 | (rawValue & 0x0000ff00) >> 8) & 255

mbient_Light = msb_value / 100.0

0x53 - Environmental VOC

Byte Description Decode
1-14

Environmental VOC

Decoding environmental  requires three (3) packages of 0x53.

The first package of 0x53 contains IAQ, State and Temperature.

The second package contains Humidity and Pressure.

The third package contains Ambient light and Noise pressure.

Each packet is 4 bytes and they are arranged as LSB (Little-endian). They must be converted to MSB (Big-endian) before you can start decoding bytes to the respective measurement.

Unit:

  • VOC: index
  • State: -
  • Temperature: Celsius
  • Humidity: %RH
  • Pressure: hPa
  • Ambient light: lux
  • Noise pressure: mV

All four bytes are used.

Endianness: LSB.

Decode package #1

Bytes 1 and 2 contain IAQ and state of the VOC sensor.

msb_value = (rawValue & 0x0000ff00) >> 8) & 255 << 8 | ((rawValue & 0x000000ff) >> 0) & 255

IAQ  = msb_value & 0x3fff

msb_value = (rawValue & 0x0000ff00) >> 8) & 255 << 8 | ((rawValue & 0x000000ff) >> 0) & 255

State = (msb_value  >> 14) & 0x03

 

Byes 3 and 4 is the temperature

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

Temperature = msb_value


Decode package #2

Bytes 1 and 2 contain Humidity

msb_value = (rawValue & 0x0000ff00) >> 8) & 255 << 8 | ((rawValue & 0x000000ff) >> 0) & 255

Humidity = msb_value / 100

 

 

Byes 3 and 4 is the temperature

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

Pressure = msb_value / 10

 


Decode package #3

Bytes 1 and 2 contain the Humidity from VOC sensor:

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

VOC_Humidity =  msb_value / 100.0

 

Bytes 3 and 4 contain the Noise pressure:

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

Noise pressure = msb_value / 10.0

0x54 - Environmental VOC and eCO2

Byte Description Decode
1-14

Environmental with eCO2

Decoding environmental  with eCO2 requires four package of 0x54.

The first package of 0x54 contains Static_IAQ (used to calculate eCO2) and the eCO2.

The second package contains VOC Index (IAQ), IAQ state and temperature.

The third package contains humidity and air pressure.

The fourth package contains ambient light and noise level.

Each packet is 4 bytes and they are arranged as LSB (Little-endian). They must be converted to MSB (Big-endian) before you can start decoding bytes to the respective measurement.

Unit:

  • eCO2: ppm
  • Sound level: dB(A)
  • Ambient light: lux
  • Temperature: Celsius
  • Humidity: %RH
  • Pressure: hPa

All four bytes are used.

Endianness: LSB.

Decode package #1

Bytes 1 and 2 contain Static IAQ, a reference measurement used by the sensor to measure eCO2:

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

Static_IAQ  = msb_value

Bytes 3 and 4 contain the eCO2 measurement:

msb_value  = (rawValue & 0x000000ff) & 255 << 8 | (rawValue & 0x0000ff00) >> 8) & 255

eCO2 = msb_value  


Decode package #2

Bytes 1 and 2 contain the VOC Index (IAQ) and the sensor State:

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

IQA = msb_value   & 0x3fff

IQA_State = (msb_value >> 14)  & 0x03

msb_value = (rawValue & 0x0000ff00) >> 16) & 255 << 8 | ((rawValue & 0x000000ff) >> 24) & 255

Bytes 3 and 4 contain the Temperature measurement from VOC sensor:

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

VOC_Temperature = rawValue / 10


Decode package #3

Bytes 1 and 2 contain the Humidity from VOC sensor:

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

VOC_Humidity =  msb_value / 100.0

Bytes 3 and 4 contain the air pressure:

msb_value  = (rawValue & 0x000000ff) & 255 << 8 | (rawValue & 0x0000ff00) >> 8) & 255

VOC_Pressure = msb_value / 10.0


Decode package #4

Bytes 1 and 2 contain the ambient light:

msb_value = (rawValue & 0x00ff0000) >> 16) & 255 << 8 | ((rawValue & 0xff000000) >> 24) & 255

Ambient_Light =  msb_value;

Bytes 3 and 4 contain the noise level:

msb_value  = (rawValue & 0x000000ff) & 255 << 8 | (rawValue & 0x0000ff00) >> 8) & 255

Noise_Level = msb_value / 10.0

0x83 - DEVICE_ID

Byte Description Decode
1-8

Two packages are needed for device ID. You will therefor find two packages of 0x83 in the payload. Totally 8 bytes (64 bits) are for ID.

ID consists of 8 digits, each byte you extracted represents each digit.

Note: ID is equivalent to UUID or EUI.

Unit: - 

Endianness: MSB.

b7 = (rawValue_from_pkg1 >> 24) & 0xff
b6 = (rawValue_from_pkg1 >> 16) & 0xff
b5 = (rawValue_from_pkg1 >> 8) & 0xff
b4 = rawValue_from_pkg1 & 0xff
b3 = (rawValue_from_pkg2 >> 24) & 0xff
b2 = (rawValue_from_pkg2 >> 16) & 0xff
b1 = (rawValue_from_pkg2 >> 8) & 0xff
b0 = rawValue_from_pkg2 & 0xff

0x84 - DEVICE_PIN

Byte Description Decode
1-4

Device Pin consists of 4 digits. Each bytes in the raw value represents a digit.

Note: ID is equivalent to UUID or EUI.

Unit: - 

Endianness: MSB.

b3 = (rawValue >> 24) & 0xff
b2 = (rawValue >> 16) & 0xff
b1 = (rawValue >> 8) & 0xff
b0 = rawValue & 0xff

0x85 - RSSI_LEVEL

Byte Description Decode
1-4

RSSI level.

Unit: dBm

Endianness: MSB.

rssi_leve = rawValue

0x86 - CELL_ID

Byte Description Decode
1-8

Cell ID of NarrowBand communication.

Unit:-

Endianness: MSB.

cell_id = rawValue