diff --git a/Examples/WirelessProgramming_OTA/OTA.py b/Examples/WirelessProgramming_OTA/OTA.py new file mode 100644 index 0000000..feee9ee --- /dev/null +++ b/Examples/WirelessProgramming_OTA/OTA.py @@ -0,0 +1,317 @@ +#********************************************************************************** +# 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 command line: python WirelessProgramming.py -f PathToFile.hex -s COM100 -t 123 +# where -t is the target ID of the Moteino you are programming +# and -s is the serial port of the programmer Moteino (on linux/osx it is something like ttyAMA0) +# To get the .hex file path go to Arduino>file>preferences and check the verbosity for compilation +# then you will get the path in the debug status area once the sketch compiles +#********************************************************************************** +# Copyright Felix Rusu, LowPowerLab.com +# Library and code by Felix Rusu - lowpowerlab.com/contact +#********************************************************************************** +# License +#********************************************************************************** +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General +# Public License as published by the Free Software +# Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will +# be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU General Public +# License for more details. +# +# You should have received a copy of the GNU General +# Public License along with this program. +# If not, see . +# +# Licence can be viewed at +# http://www.gnu.org/licenses/gpl-3.0.txt +# +# Please maintain this license information along with authorship +# and copyright notices in any redistribution of this code +# ********************************************************************************** +import time, sys, serial +import collections +import re + +### GENERAL SETTINGS ### +SERIALPORT = "COM100" # the default com/serial port the receiver is connected to +BAUDRATE = 115200 # default baud rate we talk to Moteino +TARGET=0 # Node ID of the Target that is being OTA reflashed +HEX = "flash.hex" # the HEX file containing the new program for the Target +LINESPERPACKET = 3 # HEX lines to send per RF packet (1,2 or 3) +retries = 2 +DEBUG = False + +# Read command line arguments +if (sys.argv and len(sys.argv) > 1): + if len(sys.argv)==2 and (sys.argv[1] == "-h" or sys.argv[1] == "-help" or sys.argv[1] == "?"): + #print " -d or -debug Turn debugging ON (verbose output)" + print " -f or -file HEX file to upload (Default: ", HEX, ")" + print " -t or -target {ID} Specify WirelessProgramming node target" + print " -l or -lines {1,2,3} HEX lines per RF packet (Default: 3)" + print " -s or -serial {port} Specify serial port of WirelessProgramming gateway (Default: ", SERIALPORT, ")" + print " -b or -baud {baud} Specify serial port baud rate (Default: ", BAUDRATE, ")" + print " -h or -help or ? Print this message" + exit(0) + + for i in range(len(sys.argv)): + #{ + #if sys.argv[i] == "-d" or sys.argv[i] == "-debug": + # DEBUG = True + if (sys.argv[i] == "-s" or sys.argv[i] == "-serial") and len(sys.argv) >= i+2: + SERIALPORT = sys.argv[i+1] + if (sys.argv[i] == "-b" or sys.argv[i] == "-baud") and len(sys.argv) >= i+2: + BAUD = sys.argv[i+1] + if (sys.argv[i] == "-f" or sys.argv[i] == "-file") and len(sys.argv) >= i+2: + HEX = sys.argv[i+1].strip() + if (sys.argv[i] == "-t" or sys.argv[i] == "-target") and len(sys.argv) >= i+2: + if sys.argv[i+1].isdigit() and int(sys.argv[i+1])>0 and int(sys.argv[i+1])<=255: + TARGET = int(sys.argv[i+1]) + else: + print "TARGET invalid (", sys.argv[i+1], "), must be 1-255." + exit(1) + if (sys.argv[i] == "-l" or sys.argv[i] == "-lines") and len(sys.argv) >= i+2: + if sys.argv[i+1].isdigit() and int(sys.argv[i+1])>0 and int(sys.argv[i+1])<=3: + LINESPERPACKET = int(sys.argv[i+1]) + else: + print "LINESPERPACKET invalid (", sys.argv[i+1], "), must be 1-3." + exit(1) + #} + +def millis(): + return int(round(time.time() * 1000)) + +def serWriteln(ser, msg): + #ser.write(msg + '\n') + ser.write(bytes((msg+'\n').encode('utf-8'))) + +HANDSHAKE_OK = 0 +HANDSHAKE_FAIL = 1 +HANDSHAKE_FAIL_TIMEOUT = 2 +HANDSHAKE_ERROR = 3 + +def waitForHandshake(isEOF=False): + now = millis() + while True: + if millis()-now < 4000: + #{ + if isEOF: + serWriteln(ser, "FLX?EOF") + else: + serWriteln(ser, "FLX?") + print "FLX?\n" + ser.flush() + rx = ser.readline().rstrip().upper() + + if len(rx) > 0: + #{ + print "Moteino: [" + rx + "]" + if rx == "FLX?OK": + print "HANDSHAKE OK!" + return HANDSHAKE_OK + elif rx == "FLX?NOK": + print "HANDSHAKE FAIL [TIMEOUT]: " + rx + return HANDSHAKE_FAIL + elif (len(rx)>7 and rx.startswith("FLX?NOK") or rx.startswith("FLX?ERR")): + print "HANDSHAKE FAIL [HEX IMG refused by target node], reason: " + rx + return HANDSHAKE_FAIL_ERROR + #} + #} + else: return HANDSHAKE_FAIL_TIMEOUT + +def waitForTargetSet(targetNode): + now = millis() + to = "TO:" + str(TARGET) + print to + serWriteln(ser, to) + ser.flush() + while True: + #{ + if millis()-now < 3000: + #{ + rx = ser.readline().rstrip() + if len(rx) > 0: + #{ + print "Moteino: [" + rx + "]" + if rx == to + ":OK": + return True + else: return False + #} + #} + #} + 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().strip() + + if (rx.upper().startswith("RFTX >") or rx.upper().startswith("RFACK >")): + #{ + print "Moteino DEBUG: " + rx + rx = "" + continue + #} + + if len(rx) > 0: + #{ + 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 0 + +# MAIN() +#if __name__ == "__main__": +try: + start = millis(); + # open up the serial port to get data transmitted to Programmer Moteino + ser = serial.Serial(SERIALPORT, BAUDRATE, timeout=1) #timeout=0 means nonblocking + ser.setDTR(False) + ser.setRTS(False) + time.sleep(2) #wait for Programmer Moteino reset after port open and potential bootloader time (~1.6s) + ser.flushInput(); +except IOError as e: + print "COM Port [", SERIALPORT, "] not found, exiting..." + exitNow(1) + +try: + if not 0 1 and currentLine[1:3] == '10' and len(currentLine)==43: + #{ + #check if next line != EOF, so we can bundle 2 lines + nextLine = content[seq+1].strip() + if nextLine != ":00000001FF" and nextLine[1:3] == "10" and len(nextLine) == 43: + #{ + #need to sum: the 2 lines checksums + address bytes of nextLine (to arrive at a correct final checksum of combined 2 lines + checksum = int(currentLine[41:43], 16) + int(nextLine[41:43], 16) + int(nextLine[3:5], 16) + int(nextLine[5:7], 16) + + #check if a third line != EOF, so we can bundle 3 lines + nextLine2 = content[seq+2].strip() + if LINESPERPACKET==3 and nextLine2 != ":00000001FF" and nextLine2[1:3] == "10" and len(nextLine2) == 43: + #{ + #need to sum: the previous checksum + address bytes of nextLine2 (to arrive at a correct final checksum of combined 3 lines + checksum += int(nextLine2[41:43], 16) + int(nextLine2[3:5], 16) + int(nextLine2[5:7], 16) + hexDataToSend = ":3" + hexDataToSend[2:(len(hexDataToSend)-2)] + nextLine[9:41] + nextLine2[9:41] + ('%0*X' % (2,checksum%256)) + bundledLines=3 + #} + else: + #{ + hexDataToSend = ":2" + hexDataToSend[2:(len(hexDataToSend)-2)] + nextLine[9:41] + ('%0*X' % (2,checksum%256)) + bundledLines=2; + #} + #} + #} + tx = "FLX:" + str(packetCounter) + hexDataToSend + print "TX > " + tx + serWriteln(ser, tx) + result = waitForSEQ(packetCounter) + #} + elif waitForHandshake(True) == HANDSHAKE_OK: + #{ + print "SUCCESS! (time elapsed: " + ("%.2f" % ((millis()-start)/1000.0)) + "s)" + exit(0); + #} + else: + #{ + print "FAIL, IMG REFUSED BY TARGET (size exceeded? verify target MCU matches compiled target)" + exit(99) + #} + + if result == 1: + #{ + seq+=bundledLines + packetCounter+=1 + #} + elif result == 2: # out of synch, retry + #{ + if retries > 0: + retries-=1 + print "OUT OF SYNC: retrying...\n" + continue + else: + print "FAIL: out of sync (are you running the latest OTA libs/sources?)" + exit(1) + #} + else: + #{ + if retries > 0: + retries-=1 + print "Timeout, retry...\n" + continue + else: + #{ + print "FAIL: timeout (are you running the latest OTA libs/sources?)" + exit(1) + #} + #} + #} + + while 1: + #{ + rx = ser.readline() + if (len(rx) > 0): print rx.strip() + #} + + elif (handshakeResponse == HANDSHAKE_FAIL_TIMEOUT): + print "FAIL: No response from Moteino programmer, is it connected to " + port + exit(1) + elif (handshakeResponse == HANDSHAKE_FAIL_TIMEOUT): + print "FAIL: No response from Moteino programmer, is it connected to " + port + exit(1) + else: + print "FAIL: No response from Moteino Target, is Target listening on same Freq/NetworkID & OTA enabled?" + exit(1) + +except IOError: + print "File [", HEX, "] not found, exiting..." + exit(1) + +finally: + #print 'FINALLY' + '\n' + ser.close() \ No newline at end of file diff --git a/Examples/WirelessProgramming_OTA/README.md b/Examples/WirelessProgramming_OTA/README.md new file mode 100644 index 0000000..8fcc02c --- /dev/null +++ b/Examples/WirelessProgramming_OTA/README.md @@ -0,0 +1,9 @@ +# Wireless Programming for Moteinos + +## How to use +Since v1.5 you can now run this app in several ways: +- natively via the WirelessProgramming.exe GUI app +- the windows GUI can also invoke the OTA.py script via embedded IronPython engine (parameters from GUI pass to the OTA.py script) +- cross platform straight from Python (2.7 runtime) by supplying parameters (run `python OTA.py -h` for details) +
+Make sure to download the entire repo ZIP, not individual files. \ No newline at end of file diff --git a/Examples/WirelessProgramming_OTA/WirelessProgramming.exe b/Examples/WirelessProgramming_OTA/WirelessProgramming.exe index 5b1878d..0f2a83b 100644 Binary files a/Examples/WirelessProgramming_OTA/WirelessProgramming.exe and b/Examples/WirelessProgramming_OTA/WirelessProgramming.exe differ diff --git a/Examples/WirelessProgramming_OTA/pythonLibs.zip b/Examples/WirelessProgramming_OTA/pythonLibs.zip new file mode 100644 index 0000000..4af1452 Binary files /dev/null and b/Examples/WirelessProgramming_OTA/pythonLibs.zip differ