Fleet management using openHAB

Problem

I am responsible for the road worthiness of a fairly large fleet of mobility devices. How can the tire pressure of the entire fleet be monitored?

Solution (using bicycles :slight_smile: as an example)

Use air pressure sensors, receive data from the sensors via SDR, forward sensor data to openHAB via mqtt, decode/transform sensor data within openHAB.

Hardware required

Software required

Installation

Install required software.
Install rtl_433 as a service:

[Unit]
Description=rtl_433 to MQTT publisher
After=network.target
[Service]
ExecStart=/home/openhabian/rtl_433/rtl_433 -X "n=solar_tpms,m=FSK_PCM,s=52,l=52,r=150,invert,preamble=aaaaa9" -F mqtt://localhost:1883,user=openhabian,pass=<your password>,retain=0,devices=sensors/rtl_433/P[protocol:255]/I[id:9999] -M newmodel -M protocol -M time:iso
Restart=always
RestartSec=15
[Install]
WantedBy=multi-user.target

The custom decoder (-X parameter) is not mandatory for the pressure sensor mentioned in the BOM, but we will later decode the raw data from the pressure sensors just for fun using Blockly and exactly these raw data are provided by this custom decoder.

Create MQTT Things in openHAB:

UID: mqtt:topic:1baf338605:4f44120acc
label: Fahrrad M
thingTypeUID: mqtt:topic
configuration: {}
bridgeUID: mqtt:broker:1baf338605
channels:
  - id: Temperatur_vorne
    channelTypeUID: mqtt:number
    label: Temperatur vorne
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P156/If5bdXXXX/temperature_C
      transformationPattern: JS:SolarTPMS_fix_temperature.js
      unit: °C
  - id: Druck_vorne
    channelTypeUID: mqtt:number
    label: Druck vorne
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P156/If5bdXXXX/pressure_kPa
      transformationPattern: JS:SolarTPMS_fix_pressure.js
      unit: kPa
  - id: Last_seen_vorne
    channelTypeUID: mqtt:datetime
    label: Last seen vorne
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P156/If5bdXXXX/time
  - id: Temperatur_hinten
    channelTypeUID: mqtt:number
    label: Temperatur hinten
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P156/I9c1eXXXX/temperature_C
      transformationPattern: JS:SolarTPMS_fix_temperature.js
      unit: °C
  - id: Druck_hinten
    channelTypeUID: mqtt:number
    label: Druck hinten
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P156/I9c1eXXXX/pressure_kPa
      transformationPattern: JS:SolarTPMS_fix_pressure.js
      unit: kPa
  - id: Last_seen_hinten
    channelTypeUID: mqtt:datetime
    label: Last seen hinten
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P156/I9c1eXXXX/time
  - id: Raw_code_vorne
    channelTypeUID: mqtt:string
    label: Raw code vorne
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P255/I9999/rows/0/data XXXX655965696a66
  - id: Raw_code_hinten
    channelTypeUID: mqtt:string
    label: Raw code hinten
    description: ""
    configuration:
      stateTopic: sensors/rtl_433/P255/I9999/rows/0/data XXXXa956a96aa55a

Use mosquitto_sub or the display unit that came with the sensors to get the sensor ids.

The data packets from the pressure sensors mentioned in the BOM are recognized as rtl_433 type 156 (and occasionally as type 201). The implementation of type 156 within rtl_433 refers to similar sensors and I was able to empirically determine how to transform the values of type 156 into values for the sensors from the BOM:

SolarTPMS_fix_temperature.js:

( function(i) { return i-5 } ) (input)

SolarTPMS_fix_pressure.js:

( function(i) { return Math.round( i / 1.38 / 32.6 * 100 ) } ) (input)

Some Results

grafik

Quality of the sensors

Transmission about once in 5 minutes (no transmission when not pressurized, AFAICT no motion de-/activation of the sensors, sensor transmission gets triggered by pressure (and temperature?) gradient, temperature resolution: 1 °C, pressure resolution: less than 1 / 32.6 * 100 kPa = 3 kPa = 0.03 bar = 0.435 psi)
Comparison of the temperature data from three sensors (at same location):

The blue one is off by about -3 °C (could be fixed by a sensor specific transformation).

Some fun with Manchester encoding

As mentioned above, the custom decoder (rtl_433 -X option) provides the raw bit patterns. Lets decode the raw data using Blockly:


var raw_code, byte_no, temp, byte, Manchester, start, pressure, nibble_to_hex, temperature, j, i;

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);

// Describe this function...
function decode_byte(raw_code, byte_no) {
  Manchester = ['a', '9', '6', '5'];
  start = (byte_no - 1) * 4 + 1;
  raw_code = raw_code.slice((start - 1), start + 4);
  logger.error(raw_code);
  byte = 0;
  for (j = 4; j >= 1; j--) {
    byte = byte + Math.pow(4, 4 - j) * ((Manchester.indexOf(raw_code.charAt((j - 1))) + 1) - 1);
  }
  return byte;
}

// Describe this function...
function byte_to_hex(byte) {
  logger.error(byte);
  nibble_to_hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
  return String(nibble_to_hex[((Math.floor(byte / 16) + 1) - 1)]) + String(nibble_to_hex[((byte % 16 + 1) - 1)]);
}


temp = itemRegistry.getItem('SolarTPMSReceiver_RawCode').getState();
temp += '';
pressure = Math.round(decode_byte(temp, 6) / 0.326);
temperature = decode_byte(temp, 7) - 55;
pressure += ' kPa ';
pressure += String(temperature);
pressure += ' °C id: ';
for (i = 1; i <= 4; i++) {
  pressure += String(byte_to_hex(decode_byte(temp, i)));
}
events.postUpdate('SolarTPMSrawcodedecoded_SolarTPMSrawcodedecoded', pressure);

grafik

5 Likes

While there are no doubt many ways to use openHAB as a convenience tool for managing this sort of thing, I’d caution about relying on OH for any direct safety related purpose.
I expect you know that, but let’s note if for others.

1 Like

Just wait for my next openHAB project: monitoring the operating parameters of a nuclear power plant … :wink:

3 Likes

I’m building my own SMR (Small Modular Reactor) in my back yard to power my Christmas Light display. Can you have your project finished before Christmas? :smirk:

1 Like

Be extremely careful when inserting the fuel rod into the moderator. Maybe a legendary German television series called ‘Loriot’ can give you some hints:

[machine translation:]
https://de-m-wikipedia-org.translate.goog/wiki/Weihnachten_bei_Hoppenstedts?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=de&_x_tr_pto=wapp

Link to video (Facebook)

BTT:

Some more fun with Manchester encoding:

The custom decoder (-X "n=solar_tpms,m=FSK_PCM,s=52,l=52,r=150,invert,preamble=aaaaa9") ensures that the raw data from the sensors get published:

sensors/rtl_433/P255/I9999/rows/0/data 695aa956a96aa55a69569655a5a5a9a595959aaa5a66a56a0

Now let’s dissect the data using BitBench:

TBH, I was pleasantly surprised that it was possible to use pure Blockly for decoding the bit pattern. A big ‘Thank you!’ to @stefan.hoehn and all contributors.

If I may make a few requests:
Add Blockly blocks for character <-> ASCII/Unicode conversions (for a more elegant Blockly version of dec <-> hex conversions) and binary logical operators (for CRC calculations without having to shell out to inline scripts).

Let’s see what Christmas will bring to blockly :santa:

<3 Stefan

1 Like

Seriously, would you mind describing what these blocks would look like by defining what the input is and what code you expect it to generate?

1 Like


grafik

character <-> Number conversions:
ord [ “A” ] → 65
char [ 65 ] → “A”

Thx, added to my todo list for next year to be added to the math blocks.

Now I was so looking forward to xor as a Christmas present … How am I supposed to get through Christmas without xor? :slight_smile:

var x, y, res, i;

// Describe this function...
function xor(x, y) {
  res = 0;
  i = 0;
  while (x != 0 || y != 0) {
    if (x % 2 == 0 && y % 2 == 1 || x % 2 == 1 && y % 2 == 0) {
      res = (typeof res == 'number' ? res : 0) + Math.pow(2, i);
    }
    i = (typeof i == 'number' ? i : 0) + 1;
    x = Math.floor(x / 2);
    y = Math.floor(y / 2);
  }
  return res;
}

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);


logger.error((xor(15, 14)));

Christmas is saved!

While testing different approaches to implementing xor, I came across a few more useful features that are currently missing from the Blockly implementation:

  • to x prepend text “Start” → “Start”+x
  • reverse a string
  • max/min of two items
  • 5 copies of “AB” → “ABABABABAB”
  • trim spaces from, generalize to: trim [text] from

Did you know that you can write your own blockly library and provide it via the marketplace? We actually try to only implement blocks into the core that are useful for lots of people. Special ones are perfect to be provided by a custom library.

Yes, I know about the marketplace. In my previous mail I shared my observations, it was not meant as an implementation request.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.