• Followup from this thread and now pimped up to be of actual use on the infamous Pixl.js.

    Hardware

    1. cheap BLE 4 enabled set of 4 thimble-sized tire pressure sensors (search for TPMS - V11 on eBay or Aliexpress):

    2. Pixl.js module

    Software

    // define your tires here (2-4)
    const tires = {
      '207bdd': {
        name: 'FL',
        minBar: 7.0,
        minBat: 20,
      },
      '30bc95': {
        name: 'FR',
        minBar: 7.0,
        minBat: 20,
      },
      '10ba52': {
        name: 'R',
        minBar: 5.5,
        minBat: 20
      }
    };
    
    const ALARM_BAR = 0x01;
    const ALARM_BAT = 0x02;
    const ALARM_LEAK = 0x04;
    const ALARM_TIME = 0x08;
    
    var dirty = 1;
    const timeout = 120;
    
    var flashTimer;
    
    function setFlash(on) {
      if(on) {
        if(flashTimer == undefined) {
          flashTimer = setInterval(function(){
            digitalPulse(LED1, 1, [100]);
          }, 1000);
        }
      }
      else {
        if(flashTimer) {
          clearInterval(flashTimer);
          flashTimer = undefined;
        }
        LED1.reset();
      }
    }
    
    
    function analyze() {
      var flash = 0;
      for(let id in tires) {
        var alarm = 0;
        var d = tires[id];
        if(d.t > 0) {
          // underpressure
          if(d.bar < d.minBar) {
            alarm |= ALARM_BAR;
          }
          // battery low
          if(d.bat < d.minBat) {
            alarm |= ALARM_BAT;
          }
          // signal loss
          if(d.t + timeout < getTime()) {
            alarm |= ALARM_TIME;
          }
          // leak
          if(d.leak) {
            alarm |= ALARM_LEAK;
          }
        }
        else {
          tires[id].t = getTime();
        }
        if(alarm > 0) {
          flash = 1;
        }
        tires[id].alarm = alarm;
      }
      setFlash(flash);
    }
    
    function out(text, x, y, inverse) {
      if(inverse) {
        g.fillRect(x, y, x + g.stringWidth(text) + 2, y + g.getFontHeight() - 1);
        g.setColor(0, 0, 0);
      }
      g.drawString(text, x + 1, y);
      g.setColor(1, 1, 1);
    }
    
    function draw() {
      g.clear();
      var y = 0;
      out('TPMS', 0, 0, 1);
      for(let id in tires) {
        y += g.getFontHeight();
        var d = tires[id];
        g.drawString(d.name, 0, y);
        if(d.bar != undefined) {
          out(d.bar.toFixed(1), 25, y, d.alarm & ALARM_BAR);
          out(d.temp.toFixed(1) + '°C', 55, y);
          out(d.bat + '%', 95, y, d.alarm & ALARM_BAT);
        }
        else {
          out('...', 50, y);
        }
        if(d.alarm & ALARM_TIME) {
          out('?', 120, y, 1);
        }
        else if(d.alarm & ALARM_LEAK) {
          out('!', 120, y, 1);
        }
      }
      g.flip();
    }
    
    function decodeData(device) {
      var d = new DataView(device.manufacturerData);
      var id = d.getUint32(2).toString(16).slice(-6);
    
      if( tires[id] != undefined ) {
        tires[id].t = getTime();
        tires[id].bar = d.getUint32(6, 1) / 100000;
        tires[id].temp = d.getUint32(10, 1) / 100;
        tires[id].bat = d.getUint8(14);
        tires[id].leak = d.getUint8(15);
        dirty = 1;
      }
    }
    
    
    function onInit() {
      require("FontDylex7x13").add(Graphics);
      g.setFontDylex7x13();
      analyze();
      draw();
    
      NRF.setScan(function(device){
        decodeData(device);
      }, {
        filters:[{
          services: ['fbb0']
        }]
      });
    
      setInterval(function(){
        if(dirty) {
          analyze();
          draw();
          dirty = 0;
        }
      }, 5000);
    }
    
    onInit();
    

    Function

    The sensors do not require a connection or traversing Services and Characteristics.
    All the data is just put into 16 bytes of manufacturerData found in each broadcast:

    • Bytes 0-5: pevice ID
    • Bytes 6-9: pressure (Pascal, Bar * 100000)
    • Bytes 10-13: temperature (°C * 100)
    • Byte 14: Battery (%)
    • Byte 15: Flat (flag - 0=OK, 1=detected)

    The sensor seems to:

    • Sleep when no pressure detected (no transmissions at all)
    • Transmit as soon as a change appears (with a few seconds delay)
    • Transmit at least every 60 seconds when pressure detected

    The set contains a card showing the last 6 hex digits of each device.
    You can copy these digits (lower case) to the object on top of the code.
    Everything from 1-4 wheels is possible. I only use 3 wheels because I own a Velomobile, but everything from unicycle to car is possible.

    For each wheel you can define an individual name, a minimum pressure minBar and a minimum battery percentage minBat.

    Underpressure, low battery, loss of signal (stale data for more than timeout seconds) and leaks are detected.
    There is a flag (I called it leak that seems to show 1 when the pressure is starting to decrease at a certain rate and/or the temperature is rising but the pressure is not).

    When any issue is detected, LED1 begins to flash and the value gets inverted.
    This is how it looks:


    1 Attachment

    • IMG_1467.JPG
About

Avatar for ChristianW @ChristianW started