Simple LORA field tests

Posted on
  • After getting fed up with the 433MHz devices, I got myself some cool Microchip RN2309A LORAWan modules, some @DrAzzy Hw and some 'scrap' things. For transmission antenna I used a DiY 1/4 Wave Ground Plane Antenna w/ 4 Aerials and for reception just the Wip part of it - made from stripped 12/3 Romex Electrical Wire. See attached pictures.

    After getting TX-RX and RX-TX right - happens in the best houses - and tests diagonally in 3 dimensions through the house, I put the transmitting station on a window sill ((grand)children books are good for leveling... ;-) ), took the receiving station in the car on the passenger seat and drove the neighborhood... (lazy adult approach) and see good reception up to almost 1000f despite obstacles (in the car, in suburbia of the 60' and 70' with a lot of tall (tree) vegetation. With proper antennas and placement, simple radio makes it a good 1000+ feet. Using a WAN would get it even further, as specs promise.

    It was neat to see it work 'right out of the box'.


    5 Attachments

    • LORA_SimpleLORA_FieldTest.png
    • LORA_tx_rx_streetView.png
    • LORA_xmitOnSill.jpg
    • LORA_rcvInCar1000to900f.jpg
    • LORA_allSetup.jpg
  • That's great - are those code-compatible with the RN2483 then?

    Unfortunately I don't have proper LoRa support here so haven't been able to 100% test the LoRaWan functionality - but I believe it does work fine.

  • They are as far as I used and verified the RN2483 Module. I did not venture into the WAN (mac commands) part. getStatus(...) may have something that is frequency wise not compatible, but I did not look into it (yet). I also did not use any configuration, for example setting transmit power, frequency, etc. For now - my next and first real field validation (with sensors) - I plan to just use plain radio functions (.radioTX(...) and .radioRX(...).

    Below is the code I used - changing the onInit() before upload for the station's specific tx or rx function. The transmitting station sends an autoText() - time in HH:MM:SS format - every 5 seconds. The receiving station does a 90% receiving duty cycle every 10 seconds. First, I just requested the RN2483 module code, but I was not successful in getting a communication link going (TX/RX-RX/TX wiring mess up). In addition I wanted to better understand the module implementation and had temporary added more output. Now, there is no need anymore for having the module code inline.

    Interesting detail may be the way do the logging 'to console' in absence of a console / when disconnected.

    If *logging to console is off, I still DO log*... using Morse code in functions lg(...) and bl():

    T logMessigIdDigit with red LED 'morse-ing' for errors, and green LED for information (ok) . Since I use setTimeouts() the messaging is not real time / synchronous, but async... with the given timing of 5 and 10 seconds respective, this is not an issue. I can say on the receiver that it was successful when I see 'morse-blinking' T4 on green LED (Long - Short, Short,Short, Short, Long blinks / Dash - Dot Dot Dot Dot Dash) and in error case, ***T3 on red LED ***. (cds is the coded string. A new log message id is appended at the end of the string and the string's first char is consumed to do the Dash and Dot timing; some extra characters do the Breaks between the characters - T and Digit - and G(reen) and R(ed) select LED2 and LED1 respective). ;-)

    // loraRN2309Test.js
    // PICO - DIL pin numbering USB to the left
    // Lora - SIL pin numbering bottom DrAzzy break out board 
    // Pin PICO           Pin LORA
    //   1 GND ------------ 8 GND (antena/bottom most right side)
    //   2 VBat             7 Vin
    //   3 3V3 ------------ 6 3.3v
    // ( 4 B3 ------------- 5 RST - reset, optional)
    //   5 B4               4 RTS (Serial)
    //   6 B5               3 CTS (Serial)
    //   7 B6 USART1 TX --- 2 RX  (Serial)
    //   8 B7 USART2 RX --- 1 TX  (Serial)
    
    // var Lora     = require("RN2483"); // same(?) as RN2309
    // module emulation  ========================================­=
    // module code from http://www.espruino.com/modules/RN2483.j­s:
    
    // module emulation
    var Lora = (function(){ // module emulation...
        var exports = {};   // ...begin 'bracket'
      
    /* Copyright (c) 2016 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. */
    
    function toHex(m) {
      m = E.toString(m);
      var hex = "";
      for (var i in m)
        hex += (m.charCodeAt(i)+256).toString(16).subst­r(-2);
      return hex;
    }
    
    function fromHex(d, startIdx) {
      var msg = "";
      for (var i=startIdx;i<d.length;i+=2)
        msg += String.fromCharCode(parseInt(d.substr(i,­2),16));
      return msg;
    }
    
    /** Connect to a RN2483.
      First argument is the serial device, second is an
      object containing:
    
      {
        reset : pin // optional
        debug : true // optional
      }
    */
    function RN2483(serial, options) {
      this.ser = serial;
      this.options = options||{};
      this.at = require("AT").connect(serial);
      if (this.options.debug) this.at.debug();
      var lora = this;
      this.at.registerLine("mac_rx 1",function(d) {
        lora.emit("message", fromHex(d,9));
      });
      this.macOn = true; // are we in LoRaWAN mode or not?
    }
    
    /// Reset, either via the reset line if defined, or by a serial command
    RN2483.prototype.reset = function(callback) {
      if (this.options.reset) {
        this.options.reset.reset();
        this.options.reset.set();
      } else {
        this.at.cmd("sys reset\r\n",1000,callback);
      }
      if (callback) callback();
    };
    
    /// Call the callback with the RN2483's firmware version
    RN2483.prototype.getVersion = function(callback) {
      this.at.cmd("sys get ver\r\n",1000,function(d) {
        if (!d) {callback();return;}
        d = d.split(" ");
        callback({
          type : d[0],
          version : d[1],
          date : d.slice(2).join(" ")
        });
      });
    };
    
    /** Call the callback with the current status as an object.
     Includes: EUI, VDD, appEUI, devEUI, band, dataRate, rxDelay1 and rxDelay2 */
    RN2483.prototype.getStatus = function(callback) {
      var status = {};
      var at = this.at;
    
      (new Promise(function(resolve) {
        at.cmd("sys get hweui\r\n",500,resolve);
      })).then(function(d) {
        status.EUI = d;
        return new Promise(function(resolve) {
          at.cmd("sys get vdd\r\n",500,resolve);
        });
      }).then(function(d) {
        status.VDD = parseInt(d,10)/1000;
        return new Promise(function(resolve) {
          at.cmd("mac get appeui\r\n",500,resolve);
        });
      }).then(function(d) {
        status.appEUI = d;
        return new Promise(function(resolve) {
          at.cmd("mac get deveui\r\n",500,resolve);
        });
      }).then(function(d) {
        status.devEUI = d;
        return new Promise(function(resolve) {
          at.cmd("mac get band\r\n",500,resolve);
        });
      }).then(function(d) {
        status.band = d;
        return new Promise(function(resolve) {
          at.cmd("mac get dr\r\n",500,resolve);
        });
      }).then(function(d) {
        status.dataRate = d;
        return new Promise(function(resolve) {
          at.cmd("mac get rxdelay1\r\n",500,resolve);
        });
      }).then(function(d) {
        status.rxDelay1 = d;
        return new Promise(function(resolve) {
          at.cmd("mac get rxdelay2\r\n",500,resolve);
        });
      }).then(function(d) {
        status.rxDelay2 = d;
        return new Promise(function(resolve) {
          at.cmd("mac get rx2 868\r\n",500,resolve);
        });
      }).then(function(d) {
        status.rxFreq2_868 = d;
        callback(status);
      });
    };
    
    /** configure the LoRaWAN parameters
     devAddr = 4 byte address for this device as hex - eg. "01234567"
     nwkSKey = 16 byte network session key as hex - eg. "01234567012345670123456701234567"
     appSKey = 16 byte application session key as hex - eg. "01234567012345670123456701234567"
    */
    RN2483.prototype.LoRaWAN = function(devAddr,nwkSKey,appSKey, callback)
    {
      var at = this.at;
      (new Promise(function(resolve) {
        at.cmd("mac set devaddr "+devAddr+"\r\n",500,resolve);
      })).then(function(d) {
        return new Promise(function(resolve) {
          at.cmd("mac set nwkskey "+nwkSKey+"\r\n",500,resolve);
        });
      }).then(function(d) {
        return new Promise(function(resolve) {
          at.cmd("mac set appskey "+appSKey+"\r\n",500,resolve);
        });
      }).then(function(d) {
        return new Promise(function(resolve) {
          at.cmd("mac join ABP\r\n",2000,resolve);
        });
      }).then(function(d) {
        callback((d=="ok")?null:((d===undefined?­"Timeout":d)));
      });
    };
    
    /// Set whether the MAC (LoRaWan) is enabled or disabled
    RN2483.prototype.setMAC = function(on, callback) {
      if (this.macOn==on) return callback();
      this.macOn = on;
      this.at.cmd("mac "+(on?"resume":"pause")+"\r\n",500,callb­ack);
    };
    
    /// Transmit a message over the radio (not using LoRaWAN)
    RN2483.prototype.radioTX = function(msg, callback) {
      var at = this.at;
      this.setMAC(false, function() {
        // convert to hex
        at.cmd("radio tx "+toHex(msg)+"\r\n",2000,callback);
      });
    };
    
    /** Transmit a message (using LoRaWAN). Will call the callback with 'null'
    on success, or the error message on failure.
    
    In LoRa, messages are received right after data is transmitted - if
    a message was received, the 'message' event will be fired, which 
    can be received if you added a handler as follows:
    
    lora.on('message', function(data) { ... });
     */
    RN2483.prototype.loraTX = function(msg, callback) {
      var at = this.at;
      this.setMAC(true, function() {
        // convert to hex
        at.cmd("mac tx uncnf 1 "+toHex(msg)+"\r\n",2000,function(d) {
          callback((d=="ok")?null:((d===undefined?­"Timeout":d)));
        });
      });
    };
    
    
    /** Receive a message from the radio (not using LoRaWAN) with the given timeout
    in miliseconds. If the timeout is reached, callback will be called with 'undefined' */
    RN2483.prototype.radioRX = function(timeout, callback) {
      var at = this.at;
      this.setMAC(false, function() {
        at.cmd("radio set wdt "+timeout+"\r\n", 500, function() {
          at.cmd("radio rx 0\r\n", timeout+500, function cb(d) {
            if (d=="ok") return cb;
            if (d===undefined || d.substr(0,10)!="radio_rx  ") { callback(); return; }
            callback(fromHex(d,10));
          });
        });
      });
    };
    
    exports = RN2483;
    
    // / module emulation
    return exports; // module emulation...
    })();           // ...end 'bracket'
    
    // app. cont.  ========================================­=
    
    var loraDbg  = true;    // Debug
    var loraRst  = B3;      // Reset
    var loraBd   = 57600;   // Baud
    var loraSer  = Serial1; // USART1
    var loraTx   = B6;      // USART1 TX
    var loraRx   = B7;      // USART1 RX
    var rxIntMs  = 10000;   // rx cycle interval in [ms]
    var rxIntId  = null;    // rx cycle interval id
    var rxDtyMs  = 9000;    // rx duty interval in [ms] (in of rxIntMs)
    var txIntMs  = 5000;    // tx cycle interval in [ms]
    var txIntId  = null;    // tx interval id
    var logging  = true;    // log activities to console
    
    var cds="";
    var cdl=LED2;
    function lg(ok,cd,obj) { // logging / LED blinking
      if (logging) console.log(obj);
      var l = cds.length;
      cds += "BB"+((ok) ? "G" : "R")+"LB"+blds[cd];
      if (l === 0) bl();
    }
    var blts= {"L":300,"S":80,"P":120,"B":450};
    var blds = ["LLLLL", "SLLLL", "SSLLL", "SSSLL", "SSSSL", "SSSSS", "LSSSS", "LLSSS", "LLLSS", "LLLLS"];
    function bl() {
      var c = cds.charAt(0);
      if (c=="G") { cdl = LED2; cds = cds.substr(1); c = cds.charAt(0); }
      else if (c=="R") { cdl = LED1; cds = cds.substr(1); c = cds.charAt(0); }
      if (c!="B") cdl.set();
      setTimeout(function(){
        cdl.reset();
        setTimeout(function(){
          cds = cds.substr(1);
          if (cds.length > 0) setTimeout(bl,1);
        },blts.P);
      },blts[c]);
    }
    
    function setup() {
      loraSetup();
      lg(1,0,"0: setup done.");
    }
    
    function loraSetup() {
      var d = loraDbg && logging; // compound d(ebug)
      loraSer.setup(loraBd, { tx:loraTx, rx:loraRx });
      lora = new Lora(loraSer, { reset:loraRst, debug: d });
      lora.reset(function(data){
        lg(1,1,"1: reset: "+data);
      });
    }
    
    function tx(txtIn) {
      var txt = "" 
              + ((txtIn)
                   ? txtIn
                   : "default tx text: " + autoText()
                );
      lora.radioTX(txt, function() { lg(1,2,"2: sent: " + txt); });
    }
    
    function txc() {
      if (txIntId) return; // already continuously sending
      txIntId = setInterval(function(){
        tx(autoText());
      },txIntMs);
    }
    
    function rx() {
      lora.radioRX(rxDtyMs, function(data) {
        if (data === undefined) {
          lg(0,3,"3: No data received");
        } else {
          lg(1,4,"4: Data: "+JSON.stringify(data));
        }
      });
    }
    
    function rxc() {
      if (rxIntId) return; // already continuously receiving
      rxIntId = setInterval(function(){
        rx();
      },rxIntMs);
    }
    
    
    function getStatus() {
      lora.getStatus(function(sta){
        lg(1,5,JSON.stringify(sta));
      });
    }
    
    function getVersion() {
      lora.getVersion(function(ver){
        lg(1,9,"9: ver: "+JSON.stringify(ver));
      });
    }
    
    
    function h() { // halt any ongoing tx / rx
      if (txIntId) { // halt continuous sending
        txIntId = clearInterval(txIntId);
      }
      if (rxIntId) { // halt continuous receiving
        rxIntId = clearInterval(rxIntId);
      }
    }
    
    function autoText() { // auto text for continous tx: time hh:mm:ss
      var d = new Date();
      hs = "0" + d.getHours();
      ms = "0" + d.getMinutes();
      ss = "0" + d.getSeconds();
      t  =     hs.substr(hs.length-2)
         +":"+ ms.substr(ms.length-2)
         +":"+ ss.substr(ss.length-2);
      return t;
    }
    
    
    function onInit() {
      logging = false;
      setup();
      txc();
    }
    
  • @allObjects , I've read (maybe not so carefully) your example code.
    I understand that you are doing node-to-node (no loraWan protocole) communication with 2 RN2309A modules.
    My question is, how can the receiver module knows that is listen to your emitter module ? I mean, wath's happen if another guy in your neighbourhood start sending "lora"message too ?

  • @larry, I'm not there yet - I was just taking a look at the RF side of it. There will be some (encrypted? / signed?) identifying information exchange with plain send / receive. I hope not to have to go full LoRa WAN with the need to setup a network managing server. It is out in the boonies, and yes it is possible someone could send messages and fool the recipient. There will be more posted as more will be developed.

  • Alright, it's noted ! - I've ordered all parts for doing some test like your's, I'll investigated too.

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

Simple LORA field tests

Posted by Avatar for allObjects @allObjects

Actions