Targeted wireless programming - Python script target ID

Started by KanyonKris, December 03, 2013, 05:32:02 PM

KanyonKris

I've come up with an easier, more convenient way to do wireless programming by adding the ability to specify the ID of the target Moteino with the Python script. I started with Felix's code and added a "-m {ID}" command to the Python script and added code to to the gateway sketch so it will receive the target ID and send the hex file to that Moteino.

This means the gateway does not need to be reprogrammed to specify a new target.  The gateway code stays the same and the user only needs to specify the target ID at the command line for the python script.

I've tested it a bit and it works for me. For those interested, give it try and report back if you find any problems.

The Python script and gateway sketch are attached and also included below.

Code  "Python script" Select
#/*
# * Copyright (c) 2013 by Felix Rusu <[email protected]>
# *
# * This file is free software; you can redistribute it and/or modify
# * it under the terms of either the GNU General Public License version 2
# * or the GNU Lesser General Public License version 2.1, both as
# * published by the Free Software Foundation.
# */

# This script will handle the transmission of a compiled sketch in the
# form of an INTEL HEX flash image to an attached gateway/master Moteino node,
# for further wireless transmission to a target Moteino node that will receive it de-HEXified and
# store it in external memory. Once received by the target (which is also loaded with a custom bootloader
# capable of reading back that image) it will reset and reprogram itself with the new sketch
#
# EXAMPLE USAGE: WirelessProgramming.py -f FILE.hex -m 10 -s COM6 -b 115200

import time, sys, serial
import collections
import re

### GENERAL SETTINGS ###
SERIALPORT = "COM6"  # the default com/serial port the receiver is connected to
BAUDRATE = 115200      # default baud rate we talk to Moteino
DEBUG = False
HEX = "flash.hex"
MOTEID = 10
retries = 2

# Read command line arguments
if (sys.argv and len(sys.argv) > 1):
  if len(sys.argv)==2 and sys.argv[1] == "-h":
    print " -d           Turn on debugging"
    print " -f {file}    HEX file to upload (Default: ", HEX, ")"
    print " -m {ID}      ID of target Moteino to be programmed (Default: ", MOTEID, ")"
    print " -s {port}    Serial port (Default: ", SERIALPORT, ")"
    print " -b {baud}    Baud rate of serial port (Default: ", BAUDRATE, ")"
    print " -h           Print this message"
    exit(0)
    
  for i in range(len(sys.argv)):
    if sys.argv[i] == "-d":
      DEBUG = True
    if sys.argv[i] == "-s" and len(sys.argv) >= i+2:
      SERIALPORT = sys.argv[i+1]
    if sys.argv[i] == "-b" and len(sys.argv) >= i+2:
      BAUD = sys.argv[i+1]
    if sys.argv[i] == "-f" and len(sys.argv) >= i+2:
      HEX = sys.argv[i+1]
    if sys.argv[i] == "-m" and len(sys.argv) >= i+2:
      MOTEID = sys.argv[i+1]
      if (MOTEID < 1) or (MOTEID > 255):
        print "ERROR: Target ID {", MOTEID, "} invalid, must be 1-255."
        exit(1)

# open up the FTDI serial port to get data transmitted to Moteino
ser = serial.Serial(SERIALPORT, BAUDRATE, timeout=1) #timeout=0 means nonblocking
time.sleep(2) #wait for Moteino reset after port open and potential bootloader time (~1.6s) 

def millis():
  return int(round(time.time() * 1000)) 

def waitForMIDok():
  now = millis()
  count = 0
  while True:
    if millis()-now < 5000:
      count += 1
      ser.flush()
      rx = ser.readline().rstrip()
      if len(rx) > 0:
        print "Moteino: [" + rx + "]"
        if rx == "MID?OK":
          print "MID HANDSHAKE OK!"
          return True
    else: return False

def waitForHandshake(isEOF=False):
  now = millis()
  count = 0
  while True:
    if millis()-now < 5000:
      count += 1
      if isEOF:
        ser.write("FLX?EOF" + '\n')
      else:
        ser.write("FLX?" + '\n')
        print "FLX?\n"
      ser.flush()
      rx = ser.readline().rstrip()
      if len(rx) > 0:
        print "Moteino: [" + rx + "]"
        if rx == "FLX?OK":
          print "HANDSHAKE OK!"
          return True
    else: return False

# return 0:timeout, 1:OK!, 2:match but out of synch
def waitForSEQ(seq):
  now = millis()
  while True:
    if millis()-now < 3000:
      rx = ser.readline()
      if len(rx) > 0:
        rx = rx.strip()
        print "Moteino: " + rx
        result = re.match("FLX:([0-9]*):OK", rx)
        if result != None:
          if int(result.group(1)) == seq:
            return 1
          else: return 2
    else: return False

    
# MAIN()
if __name__ == "__main__":
  try:
    # send ID of target Moteino
    ser.flush()
    tx = "MID:" + str(MOTEID)
    print "TX > " + tx
    ser.write(tx + '\n')
    if waitForMIDok():
      print "MID OK received"
    else:
      print "No response from gateway Moteino, exiting..."
      exit(1)
    
    with open(HEX) as f:
      print "File found, passing to Moteino..."
      
      if waitForHandshake():
        seq = 0
        content = f.readlines()

        while seq < len(content):
          tx = "FLX:" + str(seq) + content[seq].strip()
          isEOF = (content[seq].strip() == ":00000001FF") #this should be the last line in any valid intel HEX file
          
          if isEOF==False:
            print "TX > " + tx
            ser.write(tx + '\n')
            result = waitForSEQ(seq)
          elif waitForHandshake(True):
            print "SUCCESS!"
            exit(0)

          if result == 1: seq+=1
          elif result == 2: continue # out of synch, retry
          else:
            if retries > 0:
              retries-=1
              print "Timeout, retry...\n"
              continue
            else:
              print "TIMEOUT, aborting..."
              exit(1)

        while 1:
          rx = ser.readline()
          if (len(rx) > 0): print rx.strip()
      else:
        print "No response from gateway Moteino, exiting..."
        exit(1)

  except IOError:
    print "File ", HEX, " not found, exiting..."
    exit(1)


Code  "Moteino Arduino sketch" Select
/*
 * Copyright (c) 2013 by Felix Rusu <[email protected]>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

// This sketch is an example of how wireless programming can be achieved with a Moteino
// that was loaded with a custom 1k Optiboot that is capable of loading a new sketch from
// an external SPI flash chip
// This is the GATEWAY node, it does not need a custom Optiboot nor any external FLASH memory chip
// (ONLY the target node will need those)
// The sketch includes logic to receive the new sketch from the serial port (from a host computer) and 
// transmit it wirelessly to the target node
// The handshake protocol that receives the sketch from the serial port 
// is handled by the SPIFLash/WirelessHEX69 library, which also relies on the RFM12B library
// These libraries and custom 1k Optiboot bootloader for the target node are at: http://github.com/lowpowerlab

#include <RFM69.h>
#include <SPI.h>
#include <SPIFlash.h>
#include <WirelessHEX69.h>

#define MYID        1   // node ID used for this unit
#define NETWORKID   250
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
//#define FREQUENCY   RF69_433MHZ
//#define FREQUENCY   RF69_868MHZ
#define FREQUENCY     RF69_915MHZ
//#define IS_RFM69HW    //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define LED         9
#define SERIAL_BAUD 115200
#define ACK_TIME    50  // # of ms to wait for an ack
#define TIMEOUT     3000
#define ENCRYPTKEY  "sampleEncryptKey"

byte targetID = 10;   // default
RFM69 radio;
char c = 0;
char input[64]; //serial input buffer
bool MIDvalid = false;

void setup(){
  Serial.begin(SERIAL_BAUD);
  radio.initialize(FREQUENCY,MYID,NETWORKID);
  //radio.encrypt(ENCRYPTKEY); //OPTIONAL
#ifdef IS_RFM69HW
  radio.setHighPower(); //only for RFM69HW!
#endif
  //Serial.print("Start wireless gateway...");
}

void loop(){
  byte inputLen = readSerialLine(input);
  
  if (inputLen == 4 && input[0]=='F' && input[1]=='L' && input[2]=='X' && input[3]=='?') {
    CheckForSerialHEX((byte*)input, inputLen, radio, targetID, TIMEOUT, ACK_TIME, true);
  }
  else if (input[0]=='M' && input[1]=='I' && input[2]=='D' && input[3]==':') {
    targetID = 0;
    for (byte i = 4; i<inputLen; i++) //up to 3 characters for target ID
    {
      if (input[i] >=48 && input[i]<=57) {
        targetID = targetID*10+input[i]-48;
        MIDvalid = true;
      }
      else {
        MIDvalid = false;
      }
    }
    if (MIDvalid) {
      Serial.println("MID?OK");
    }
    else {
      Serial.print("Bad MID");
    }
  }
  else if (inputLen>0) { //just echo back
    Serial.print("SERIAL IN > ");Serial.println(input);
  }
  
  if (radio.receiveDone())
  {
    for (byte i = 0; i < radio.DATALEN; i++)
      Serial.print((char)radio.DATA[i]);
    
    if (radio.ACK_REQUESTED)
    {
      radio.sendACK();
      Serial.print(" - ACK sent");
    }
    
    Serial.println();
  }
  Blink(LED,5); //heartbeat
}

void Blink(byte PIN, int DELAY_MS)
{
  pinMode(PIN, OUTPUT);
  digitalWrite(PIN,HIGH);
  delay(DELAY_MS);
  digitalWrite(PIN,LOW);
}

Felix

Kris,
Many thanks for this great contribution, hope everyone who uses wireless programming will find this useful.
I edited your post to make it more compact and include everything in 1 entry.
Otherwise how do you find wireless programming to be working? Reliable, reasonably fast?

KanyonKris

Felix, thanks for reformatting my post, much better.

I've only wireless programmed 3-4 times, no problems yet. My sketches have been pretty small and take about 15 seconds to upload. Pretty cool to see all the LEDs blinking as a new program flies through the air, then the lights stop and the target Moteino reboots and runs the new sketch. It's like magic.