NRF.setAdvertising() duration option

Posted on
  • I am using the Puck to advertise multiple sensors and to do this I set up a delay to sequence each sensor value. When monitored on node-red it would appear that the advertised value is only sent once or again if the value changes in the time before the next sensor value is advertised.

    This is OK for most uses, but if the Pi misses an advertisement data can be lost, like a motion sensor pulse.

    The question I have is, is an advertise duration option available? I know the advertise interval is, but how long it advertises for the set interval before it stops I am not sure.

    When you monitor RSSI it appears to be a constant stream of advertisements. Is this possible to set up with NRF.

    Perhaps it is set to only re-advertise onChange()?

  • Puck.js should just keep advertising what you ask it to forever - so a duration doesn't really make sense. If you want to change what is advertised after some period then you could just use setTimeout or setInterval to call setAdvertising again?

  • OK Thanks Gordon,

    I have tested that the advertising does indeed continue. The data doesn't change until you call the function again.

    What appears to be crucial to ensure the data has a fair chance of being picked up is to stagger the advertising interval and different setTimeout() periods that do not overlap too much. Setting them to the same interval tends to block each other.

    I remember reading in some NRF documentation that there is a random figure deployed in the advertising and switching of one of the three advertising channels to avoid collisions. I am also sure there was a duration setting as well to stop the advertising after a period of time.

    What would be the method to stop any previous advertising setup to reduce power and reduce the possibility of collisions if any?

    One other method to achieve this would be in the payload data rather than the channel as in a key pair payload. This would reduce the data allowed but for telemetry data should be fine.

  • I think you're right about the pseudo-randomness - you'd need to leave a gap, and I imagine that setAdvertising causes the first advertising packet to be sent after the first advertising interval rather than immediately.

    So you're actually trying to cycle through types of advertising data perhaps a few times a second? I think that'll probably hit the power usage quite hard.

    In my 'to do' list I am planning on letting setAdvertising take an array of advertising packets which it'd cycle through - I imagine that'd probably work best for you?

    I am also sure there was a duration setting as well to stop the advertising after a period of time.

    There was, but internally it's handled in pretty much the same way as setTimeout. I don't think it'd save you much.

    I haven't looked into setting the payload data - it's possible it's not too painful to expose in JS, although I guess you have to explicitly request it in order to read it?

  • All what you say makes sense Gordon following on from my recent tests.

    I may help to make it clear what I am attempting and how I am progressing so far.

    The project is to set a range of Puck's to broadcast primarily motion detection but also temperature, light level, magnetometer, voltage readings and battery condition. The target uses are:

    a) Motion detection to turn lights on and off and act as intrusion detection.

    b) Pool water level detection and temperature using a floating magnet in the pool skimmer and a waterproof remote temperature IC. (Currently working using a Photon device and a solar panel / battery circuit)

    c) Soil moisture detection to trigger irrigation and monitor soil moisture over time.

    All of this was to be monitored, recorded and acted on using an externally situated Pi3 running node-red. Trigger actions are handled with a combination of IFTTT event handlers and notifications, and data logging using an SQL database.

    I must say that I am very happy with what has been achieved so far and most of the elements are working very well, but I feel it's not fully optimised yet.

    Initially I experimented with code that you posted to set up advertising a simple value:

    setInterval(function() {
      NRF.setAdvertising({
        0x1809 : [Math.round(E.getTemperature())]
      });
    }, 30000);
    

    It's clear now that this will set broadcasting up continuously the temperature reading, and update it every 30 seconds. All very good.

    I modified the code to send a second parameter to see if it was possible to send multiple sensors in the same broadcast command. For example:

    setInterval(function() {
      NRF.setAdvertising({
        0x1809 : [Math.round(E.getTemperature())],
        0x2a00 : [Math.floor((Math.random() * 10) + 1)]
      });
    }, 30000);
    

    Setting up a /ble/advertise/fd:f9:15:7f:43:07/# MQTT listener showed both values correctly and were also filterable using /0X809 and /0x2a00 in the filter.

    Adding a third or fourth value also worked until I more data to the array like a string of characters to represent the mag array, or just a random "Hello world" string. This broke the advertisement and threw an error in the Puck console. I believe it was a ble size error.

    Removing some of the data lines or shortening the strings sent fixed the error and all was good once more.

    To get around the problem, I divided the messaging up into multiple advertisements delayed by 5 seconds using setTimeout() functions. All these were wrapped in a 60 second setInterval() function to refresh the data readings.

    One other important discovery was that if you want to advertise an important event, you must stop other events from being advertised, or at least being refreshed, otherwise the event may be lost. This was the case for a motion detect event. To handle this I recorded state of the motion detector to suppress broadcasting of other data.

    Motion detection was also set up to be broadcast on a state change using a setWatch() function. This was the number one priority broadcast event so had to broadcast as soon as it occurred.

    Using this method led me to believe there is an advertise period (backed up by some NRF documents). The fully working code I used is:

    //7f:9a Motion detector code
    digitalPulse(LED1,true,1000);
    pinMode(D1, 'input_pulldown');
    pinMode(D30, 'input');
    var state =0;
    var zero = Puck.mag();
    
    NRF.setTxPower(4); // Full Power advertising
    
    
    function motion(){
      digitalPulse(LED3,true,100);
      advertise(1);
    }
    
    function reset(){
      digitalPulse(LED2,true,100);
      advertise(0);
    }
    
    function advertise(pinState){
      state = pinState;
      NRF.setAdvertising({
              0x2a00 : [state]
          });
      
    }
    
    setWatch(motion, D1, {repeat:true,edge:'rising',debounce:100});
    setWatch(reset, D1, {repeat:true,edge:'falling',debounce:100});
    
    setInterval(function () {
      
      
      setTimeout(function(){
        if(state === 0){
          NRF.setAdvertising({
            0x2a01 : [String(E.getTemperature())]
          });
        }
      },1);
    
        setTimeout(function(){
          var volts = analogRead(D30)*6.6;
              volts = Math.round(volts * 100) / 100;
         if(state === 0){
          NRF.setAdvertising({
            0x2a02 : [String(volts)]
          });
         }
        },5000);
    
       setTimeout(function(){
          if(state === 0){
            NRF.setAdvertising({
              0x180f : [Puck.getBatteryPercentage()]
            });
          }
        },10000);
      
      setTimeout(function(){
          if(state === 0){
            NRF.setAdvertising({
              0x2a03 : [String(NRF.getBattery())]
            });
          }
        },15000);
      
        setTimeout(function(){
          if(state === 0){
            NRF.setAdvertising({
              0x2a04 : [String(Puck.light())]
            });
          }
        },20000);
      
        setTimeout(function(){
         if(state === 0){
          NRF.setAdvertising({
            0x2a05 : [String(getMag())]
          });
         }
        },25000);
    
    
    },60000);
    
    function getMag(){
      p = Puck.mag();
      p.x -= zero.x;
      p.y -= zero.y;
      p.z -= zero.z;
      var s = Math.sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
       return Math.round(s * 100 ) / 100;
    }
    

    If anyone would like to see the node-red flow and view some of the output data I have attach then to this post.

    Finally a note about the battery life. If you simply watch all events from a Puck, you see a stream of RSSI adverts with the puck name:

    9/01/2017, 11:06:3194b19651.0be4b8
    /ble/advertise/fd:f9:15:7f:43:07 : msg.payload : string [34]
    {"rssi":-99,"name":"Puck.js 4307"}
    19/01/2017, 11:06:3194b19651.0be4b8
    /ble/advertise/fd:f9:15:7f:43:07/rssi : msg.payload : string [3]
    -99

    These occur continously, so the thought of of being able to have a multiplexing array of adverts that use the same broadcast stream would be really cool. Having it all handled in the command NRF.setAdvertising({id,[payload1]},{id,[payload2]},id{id,[payload3]}, options) whoud be a simple way to set up non colliding event data as you suggested. Further options could be added to have a priority flag that ensured a motion event could be prioritised over another to be re-advertised on change.

    My current Puck battery life is holding out apart from initial uploading hundreds of times the code, but when left to advertise, appears to be steady. Temperature does effect the battery readings so you do have to watch trends over time.
    All food for thought and possibly outside the normal methods of ble data coms.


    2 Attachments

  • Thanks for the explanation - very neat graphs as well!

    For motion, did you consider sending a byte value that increments when there is motion? That way you'll always know if there was motion, unless you manage to miss 256 packets in a row!

    Also, by removing the name (see http://www.espruino.com/Reference#l_NRF_setAdvertising) you can get a little bit more space for advertising data.

    With a bit of care you could probably convert all your readings into bytes (ranged 0 to 255) and stick them all in one big characteristic:

     NRF.setAdvertising({
              0x2a00 : [motion,volts,light,temp,sensor]
          });
    

    Then decode it on the Pi - either my modifying EspruinoHub or maybe just with node-red.

    It'd be a lot more efficient than sending ASCII strings.

    The current method will work well though - but I wouldn't bank on getting the full 1 year battery life out of it :)

  • Thanks for the really helpful comments Gordon.

    The strings conversions was principally to overcome floating point numbers from the temperature readings (- minus values) and decimal points in voltage readings, but I guess I should move on and byte convert the values as much of the ble specification is set up as. Most coding is based on a quick and dirty work around. I had to write some code in node-red to convert strings to floats (see the flows attached in my last post) but I actually like the idea of compressing it all into bytes.

    I will need to think about priority queuing, but I am sure using a setWatch() function and increment counter as you suggest, I would get a really solid working model. I actually forgot that I had written some code years ago that counted the motion events over time, and only reacted if the value exceed a threshold. This reduced the noise you get when wind occasionally set the PIR detector.

    OK, V2 is now under way using an array of bytes with some code to convert values back. At worst I can use one advertising command for motion and a less frequent less important advert for cumulative data.

  • It looks like you can just use characteristic.getValue() to return the array of bytes:

    https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue()

  • Thank you Gordon.

  • Hi Guys,

    Movement detection code from above is uploaded on Puck with firmware 1v91, but it does not detect any movement. Please advice.

    //7f:9a Motion detector code
    digitalPulse(LED1,true,1000);
    pinMode(D1, 'input_pulldown');
    pinMode(D30, 'input');
    var state=0;
    
    NRF.setTxPower(4); // Full Power advertising
    
    function motion(){
      digitalPulse(LED3,true,100);
      advertise(1);
    }
    
    function reset(){
      digitalPulse(LED2,true,100);
      advertise(0);
    }
    
    function advertise(pinState){
      console.log(pinState);
      state = pinState;
      NRF.setAdvertising({
        0x2a00 : [state]
      });
    }
    
    setWatch(motion, D1, {repeat:true,edge:'rising',debounce:100});
    setWatch(reset, D1, {repeat:true,edge:'falling',debounce:100});
    

    Thank you.

  • You mean the LEDs don't flash?

    Have you tried just manually connecting D1 to GND and 3V and seeing if the LEDs flash then?

    If so, there could be a wiring problem? Or maybe it needs input_pullup not input_pulldown?

  • Hi Gordon,

    Connecting D1 to GND make it work.

    Would you recommend a sensor for movement which could be integrated into the Puck ?

    Thank you.

  • You can use the built-in magnetometer to detect when the magnetic field changes?

    Or you mean detecting movement around the Puck, like a PIR?

  • Hi Gordon,

    I am interested whether or not the Puck is in motion. Should I use magnetic field values to detect that ?

  • It'd be worth trying. Take a look at this - it seems to work pretty well: http://forum.espruino.com/conversations/301185/#comment13502846

    Otherwise there are simple mechanical sensors like this: https://www.adafruit.com/product/1766

    or this: https://www.adafruit.com/products/173

    But they're not perfect themselves as they often don't detect motion in all axes.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

NRF.setAdvertising() duration option

Posted by Avatar for Eric @Eric

Actions