Detecting movement

Posted on
  • A few people have asked about having an accelerometer so they can detect movement - but it's not the only way.

    For a lot of things you can still use the magnetometer. The magnetic field changes a lot, not just with angle, but with what other items the Puck.js is near. It's almost impossible to move Puck.js without changing the magnetometer's reading at least a little.

    For instance try this code:

    var avr = Puck.mag();
    var magDiff;
    Puck.on('mag', function(xyz) {
      // work out difference in field
      var dx = xyz.x-avr.x;
      var dy = xyz.y-avr.y;
      var dz = xyz.z-avr.z;
      magDiff = Math.sqrt(dx*dx+dy*dy+dz*dz);
      // update average
      avr.x += dx/2;
      avr.y += dy/2;
      avr.z += dz/2;  
      LED.write(magDiff > 50);

    It looks for changes in the magnetic field about once a second, and will light the red LED if it has changed more than a little. After a few seconds of no movement the LED will turn off.

    It's easy, low power, and seems to work pretty well.

  • I am trying to use this, but every now and then the magnet readings keep moving. Especially when I have a magnet close sometimes it doesn't come to a stop. The difference keeps reading at about 200-700.

    Anything you know I could do about this?

  • You could try just increasing the threshold value > 50 in the above code? Or how close is the magnet nearby? You could make the threshold value dependent on the strength of the magnetic field - since the noise in the reading may well be proportionate to the size of the field.

    Otherwise if the readings keep varying randomly you could try keeping a short term average of the readings and comparing that to work around noise? This isn't tested, but something like it should work:

    var avr = Puck.mag(); // long average
    var savr = Puck.mag(); // short average
    var magDiff;
    Puck.on('mag', function(xyz) {
      // update short average
      var dx = xyz.x-savr.x;
      var dy = xyz.y-savr.y;
      var dz = xyz.z-savr.z;
      savr.x += dx/2;
      savr.y += dy/2;
      savr.z += dz/2;  
      // work out difference in field
      var dx = savr.x-avr.x;
      var dy = savr.y-avr.y;
      var dz = savr.z-avr.z;
      magDiff = Math.sqrt(dx*dx+dy*dy+dz*dz);
      // update average
      var dx = xyz.x-avr.x;
      var dy = xyz.y-avr.y;
      var dz = xyz.z-avr.z;
      avr.x += dx/8;
      avr.y += dy/8;
      avr.z += dz/8;  
      LED.write(magDiff > 50); // this value might need lowering

    You could also do a median filter, which is relatively easy to do with the built-in array.sort.

  • I already have a couple Espruino wifi boards but am looking to get a Puck due to it's small form factor to use to track a persons movement. Something like a fitness tracker. Would using the magnetometer in the fashion described above allow you to track things like arm motions0 and get values that could be used like an accelerometers x,y,z values?

  • Hi! You couldn't get something that was identical to the accelerometer, but you can tell reasonably well when someone is moving or not.

    You're basically getting values that tell you where North is - so you can detect changes in rotation but not lateral movements, in the same way the accelerometer won't detect rotation but will detect lateral movement.

    Honestly you'd have to try and see if it did what you needed - but worst case you could actually add an accelerometer - or there are devices like the Nordic Thingy that can run Espruino and are loaded with all the sensors you could possibly want (there should be docs and binaries for it in the next month or so).

  • Cool, makes sense. That's what I figured but was not sure if I was missing something.
    Also, thanks for the info on Nordic Thingy. I never heard of that one and it looks neat but for me, I'm going to go with the Puck + external accelerometer.

  • I noticed that when I stop moving my puck the magDiff value may start getting higher and after some seconds starts getting lower.. What can i do for that?

  • If you've detected movement (magDiff is higher than your threshold) you can set avr.x=savr.x; (and for y and z), which will mean that for the next iteration it won't report movement unless the magnetic field has changed again.

    However you're likely to then get some iterations when movement isn't reported when the device has actually moved.

    The other thing you can do is lower the period of the average (eg change avr.x += dx/8; to avr.x += dx/4; (for y and z too)) which will make it less sensitive to movement but will report that it is stationary more quickly.

  • The Puck.onMag function returns an orientation about once per second. Is it possible to speed that up somehow? I can get faster readings by repeatedly calling Puck.mag(), so it's possible. (Although if I ask for too many, too fast the program crashes.) That said, I would prefer the event-based polling. Is there a time constant somewhere that could be changed?

    Here is the code that generates the overflow, btw.

    var x=0;
    var y=0;
    var z=0;
    var f;

    var zero = Puck.mag();

    f = Puck.mag();
    x = f.x - zero.x;
    y = f.y - zero.y;
    z = f.z - zero.z;

  • Sure - you just specify a number in Puck.magOn, like Puck.magOn(5) - seeĀ­_magOn

    What kind of overflow did you get?

    If you do while(true) then the code won't exit and no other JS tasks will be able to get run - you'll have to Ctrl-C out of it. On Puck.js it's especially painful because it can't transmit that quickly, so
    when you're outputting with console.log it's basically just stuck for 99% of the time waiting for the next Bluetooth send task to finish.

  • Thank you! Works like a charm.

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

Detecting movement

Posted by Avatar for Gordon @Gordon