Temperature Sensor Encoding

I’m trying to integrate some custom AC control software with Velbus.

I see that the current temperature is sent onto the bus for many nodes with a built in sensor, such as the VMBGP4-20. Typically this will be by sending a message of type 0xE6 “COMMAND_SENSOR_TEMPERATURE”

The module protocol shows Databytes 2 and 3 transmitting the current temperature in two’s complement format.
[This is effectively the whole number of the current temp (in degrees C) is divided by the resolution (0.0625 degrees).]

There is a table showing the two bytes for various sample temps. There are a couple of typos which I found using the calculator at Two's (2s) Complement Calculator to understand how this works.

  1. Let’s start with the positive values. The values for 0.5, 0.25, 0.125 and 0,0625 are just the binary format of the multiples of the resolution i.e. 8, 4, 2, and 1 - being shifted 5 bits to the left. They are not two’s complement and require not complex maths.

  2. The next value is the one shown for 63.5 - this is 1016 times the resolution. The binary version of this is 0b0011 1111 1000 , shift this 5 bits to the left gives 0111 1111 0000 0000 which is not what is in the table.
    Taking the value in the table “0111 1111 111x xxxx” and shifting 5 bits to the right gives 0b0011 1111 1111 which is decimal 1023. 1023 x 0.0625 = 63.9375 degrees - close but just enough to be misleading.

  3. The negative values are the twos complement of the multiple with the sign removed.
    So for -0.0625 this is -1, which becomes 1 and the two complement is 111 1111 1111. Shift this 5 bits to the left and the values line up with the table.
    The same sort of thing works for -0.125 (-2 times resolution), -0.25 (-4 times resolution).

The value shown for -0.5 appears to be wrong. This is actually -8 times the resolution. The twos comp calculator shows 111 1111 1000 for 8. Shift this 5 bits to the left gives 0b 1111 1111 0000 0000.
The value shown in the table is “1111 1110 000x xxxx” (flip the bits and shift it right and and one) it becomes 0b0001 0000 which is actually 16 times the resolution, or -1.0 degrees.

For -55 (which is 880) the calculator shows 1001 0010 000. Shift this 5 bits to the left for 0b1001 0010 000x xxxx which is correct.

I think the negative values could go as high as the positive with a transmitted value of “1000 0000 000x xxxx” which can be translated back to -1023 (x 0.0625) that gives -69.9375 degrees.
Whether this would be of practical use apart from Arctic conditions is a moot point though :slight_smile:

Anyway I hope this helps people get their heads around how these temperature readings work (along with the small corrections required).

Can you add the table to have all info in one place? I want to see where is the error. I’d expect this for binary representation of real numbers, 4 bits for after the dot.

That’s a lot of number to compute and display.

I’ve just worked around the limits. Here’s some code if you want to play around.

bool vmbTemperatureFromSensor(byte outTemp[2], float sensorTemperature)
{
  float precision = 0.0625;
  }  
  // work out the multiple
  int16_t multiple = sensorTemperature / precision;
  
  // if positive return the multiple
  if (multiple >= 0)
  { 
    multiple = multiple << 5;
    outTemp[0] = multiple / 256;
    outTemp[1] = (multiple % 256);
    return true;
  } else {
    uint16_t twos = ((~(-multiple)) + 1) << 5;
    outTemp[0] = (twos >>8);
    outTemp[1] = (twos & 0xFF);
    return true;
  } 
  return false;
}

Call it in a loop ?

  uint8_t currentTemp[2];
  for (int i=-1023; i < 1024; i++) {
    if (vmbTemperatureFromSensor(currentTemp,  i))
    {
      // do something with currentTemp[0] and  currentTemp[1]
    }   
  }

FWIW you don’t need to do any fancy operations for two’s complement computation on pretty much every contemporary machine. You can interpret the bytes as a signed 16-bit number which are internally represented as two’s complement, so the code ends up as:

fn parse_temperature(inp: [u8; 2]) -> f32 {
    0.0625 * (i16::from_be(inp) >> 5) as f32
}

or in C:

[EDIT: the code was wrong, see below for a fixed version]

The shift and multiply can also be folded together into 0.001953125 but that hurts readability tad a bit too much (and the compilers will figure this out for you for the most part anyway.)

2 Likes

The C version does not compile. And when I add additional parenthesis it returns incorrect values.

“C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.”

Can I ask to provide the code, compilation ready and the values that are incorrect? It will facilitate the discussion.

Yeah, my bad, I wrote this ad-lib and didn’t test it out. Turns out the shift by 5 was not shifting with sign extension even though it was shifting a signed value. I blame the darned auto-conversions to int. Here’s the fixed code and a playbox:

float parse_temperature(const uint8_t in[2]) {
    short word = ((short)in[0] << 8) | (short)in[1]; // can also use ntohs from arpa/inet.h
    return 0.0625 * (float)(word >> 5);
}

But really just use something other than C… You can already see how my attempt to use it as a lingua franca has backfired…

2 Likes

Nice work and thanks. Un/Fortunately I’m doing this with an Arduino so C/C++ is what works at the moment.

As each language has it own syntax, here is JavaScript:

function FineTempCalculation(partA, partB) {
	return partA / 2 - Math.round(((4 - partB) >> 5) * 0.0625 * 10) / 10
}

// Function to calculate temperature with high precision
function TempCurrentCalculation(msg) {
	// E6 (Transmit Temp) or EA (Sensor status)
	switch (msg[4]) {
		case 0xE6:
			return FineTempCalculation(msg[5], msg[6])
		case 0xEA:
			return FineTempCalculation(msg[8], msg[9])
		default:
			console.error("ERROR with TempCalculation", msg)
			return undefined
	}
}

They’re two functions, because they’re two ways to receive a temperature from Velbus.

3 Likes