Skip to content

Commit 2da600e

Browse files
committed
WirelessProgramming 1.5 + OTA.py
1 parent be3ffaa commit 2da600e

File tree

4 files changed

+326
-0
lines changed

4 files changed

+326
-0
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
#**********************************************************************************
2+
# This script will handle the transmission of a compiled sketch in the
3+
# form of an INTEL HEX flash image to an attached gateway/master Moteino node,
4+
# for further wireless transmission to a target Moteino node that will receive it de-HEXified and
5+
# store it in external memory. Once received by the target (which is also loaded with a custom bootloader
6+
# capable of reading back that image) it will reset and reprogram itself with the new sketch
7+
#
8+
# EXAMPLE command line: python WirelessProgramming.py -f PathToFile.hex -s COM100 -t 123
9+
# where -t is the target ID of the Moteino you are programming
10+
# and -s is the serial port of the programmer Moteino (on linux/osx it is something like ttyAMA0)
11+
# To get the .hex file path go to Arduino>file>preferences and check the verbosity for compilation
12+
# then you will get the path in the debug status area once the sketch compiles
13+
#**********************************************************************************
14+
# Copyright Felix Rusu, LowPowerLab.com
15+
# Library and code by Felix Rusu - lowpowerlab.com/contact
16+
#**********************************************************************************
17+
# License
18+
#**********************************************************************************
19+
# This program is free software; you can redistribute it
20+
# and/or modify it under the terms of the GNU General
21+
# Public License as published by the Free Software
22+
# Foundation; either version 3 of the License, or
23+
# (at your option) any later version.
24+
#
25+
# This program is distributed in the hope that it will
26+
# be useful, but WITHOUT ANY WARRANTY; without even the
27+
# implied warranty of MERCHANTABILITY or FITNESS FOR A
28+
# PARTICULAR PURPOSE. See the GNU General Public
29+
# License for more details.
30+
#
31+
# You should have received a copy of the GNU General
32+
# Public License along with this program.
33+
# If not, see <http://www.gnu.org/licenses/>.
34+
#
35+
# Licence can be viewed at
36+
# http://www.gnu.org/licenses/gpl-3.0.txt
37+
#
38+
# Please maintain this license information along with authorship
39+
# and copyright notices in any redistribution of this code
40+
# **********************************************************************************
41+
import time, sys, serial
42+
import collections
43+
import re
44+
45+
### GENERAL SETTINGS ###
46+
SERIALPORT = "COM100" # the default com/serial port the receiver is connected to
47+
BAUDRATE = 115200 # default baud rate we talk to Moteino
48+
TARGET=0 # Node ID of the Target that is being OTA reflashed
49+
HEX = "flash.hex" # the HEX file containing the new program for the Target
50+
LINESPERPACKET = 3 # HEX lines to send per RF packet (1,2 or 3)
51+
retries = 2
52+
DEBUG = False
53+
54+
# Read command line arguments
55+
if (sys.argv and len(sys.argv) > 1):
56+
if len(sys.argv)==2 and (sys.argv[1] == "-h" or sys.argv[1] == "-help" or sys.argv[1] == "?"):
57+
#print " -d or -debug Turn debugging ON (verbose output)"
58+
print " -f or -file HEX file to upload (Default: ", HEX, ")"
59+
print " -t or -target {ID} Specify WirelessProgramming node target"
60+
print " -l or -lines {1,2,3} HEX lines per RF packet (Default: 3)"
61+
print " -s or -serial {port} Specify serial port of WirelessProgramming gateway (Default: ", SERIALPORT, ")"
62+
print " -b or -baud {baud} Specify serial port baud rate (Default: ", BAUDRATE, ")"
63+
print " -h or -help or ? Print this message"
64+
exit(0)
65+
66+
for i in range(len(sys.argv)):
67+
#{
68+
#if sys.argv[i] == "-d" or sys.argv[i] == "-debug":
69+
# DEBUG = True
70+
if (sys.argv[i] == "-s" or sys.argv[i] == "-serial") and len(sys.argv) >= i+2:
71+
SERIALPORT = sys.argv[i+1]
72+
if (sys.argv[i] == "-b" or sys.argv[i] == "-baud") and len(sys.argv) >= i+2:
73+
BAUD = sys.argv[i+1]
74+
if (sys.argv[i] == "-f" or sys.argv[i] == "-file") and len(sys.argv) >= i+2:
75+
HEX = sys.argv[i+1].strip()
76+
if (sys.argv[i] == "-t" or sys.argv[i] == "-target") and len(sys.argv) >= i+2:
77+
if sys.argv[i+1].isdigit() and int(sys.argv[i+1])>0 and int(sys.argv[i+1])<=255:
78+
TARGET = int(sys.argv[i+1])
79+
else:
80+
print "TARGET invalid (", sys.argv[i+1], "), must be 1-255."
81+
exit(1)
82+
if (sys.argv[i] == "-l" or sys.argv[i] == "-lines") and len(sys.argv) >= i+2:
83+
if sys.argv[i+1].isdigit() and int(sys.argv[i+1])>0 and int(sys.argv[i+1])<=3:
84+
LINESPERPACKET = int(sys.argv[i+1])
85+
else:
86+
print "LINESPERPACKET invalid (", sys.argv[i+1], "), must be 1-3."
87+
exit(1)
88+
#}
89+
90+
def millis():
91+
return int(round(time.time() * 1000))
92+
93+
def serWriteln(ser, msg):
94+
#ser.write(msg + '\n')
95+
ser.write(bytes((msg+'\n').encode('utf-8')))
96+
97+
HANDSHAKE_OK = 0
98+
HANDSHAKE_FAIL = 1
99+
HANDSHAKE_FAIL_TIMEOUT = 2
100+
HANDSHAKE_ERROR = 3
101+
102+
def waitForHandshake(isEOF=False):
103+
now = millis()
104+
while True:
105+
if millis()-now < 4000:
106+
#{
107+
if isEOF:
108+
serWriteln(ser, "FLX?EOF")
109+
else:
110+
serWriteln(ser, "FLX?")
111+
print "FLX?\n"
112+
ser.flush()
113+
rx = ser.readline().rstrip().upper()
114+
115+
if len(rx) > 0:
116+
#{
117+
print "Moteino: [" + rx + "]"
118+
if rx == "FLX?OK":
119+
print "HANDSHAKE OK!"
120+
return HANDSHAKE_OK
121+
elif rx == "FLX?NOK":
122+
print "HANDSHAKE FAIL [TIMEOUT]: " + rx
123+
return HANDSHAKE_FAIL
124+
elif (len(rx)>7 and rx.startswith("FLX?NOK") or rx.startswith("FLX?ERR")):
125+
print "HANDSHAKE FAIL [HEX IMG refused by target node], reason: " + rx
126+
return HANDSHAKE_FAIL_ERROR
127+
#}
128+
#}
129+
else: return HANDSHAKE_FAIL_TIMEOUT
130+
131+
def waitForTargetSet(targetNode):
132+
now = millis()
133+
to = "TO:" + str(TARGET)
134+
print to
135+
serWriteln(ser, to)
136+
ser.flush()
137+
while True:
138+
#{
139+
if millis()-now < 3000:
140+
#{
141+
rx = ser.readline().rstrip()
142+
if len(rx) > 0:
143+
#{
144+
print "Moteino: [" + rx + "]"
145+
if rx == to + ":OK":
146+
return True
147+
else: return False
148+
#}
149+
#}
150+
#}
151+
return False
152+
153+
# return 0:timeout, 1:OK!, 2:match but out of synch
154+
def waitForSEQ(seq):
155+
now = millis()
156+
while True:
157+
if millis()-now < 3000:
158+
rx = ser.readline().strip()
159+
160+
if (rx.upper().startswith("RFTX >") or rx.upper().startswith("RFACK >")):
161+
#{
162+
print "Moteino DEBUG: " + rx
163+
rx = ""
164+
continue
165+
#}
166+
167+
if len(rx) > 0:
168+
#{
169+
print "Moteino: " + rx
170+
result = re.match("FLX:([0-9]*):OK", rx)
171+
if result != None:
172+
if int(result.group(1)) == seq:
173+
return 1
174+
else: return 2
175+
#}
176+
else: return 0
177+
178+
# MAIN()
179+
#if __name__ == "__main__":
180+
try:
181+
start = millis();
182+
# open up the serial port to get data transmitted to Programmer Moteino
183+
ser = serial.Serial(SERIALPORT, BAUDRATE, timeout=1) #timeout=0 means nonblocking
184+
ser.setDTR(False)
185+
ser.setRTS(False)
186+
time.sleep(2) #wait for Programmer Moteino reset after port open and potential bootloader time (~1.6s)
187+
ser.flushInput();
188+
except IOError as e:
189+
print "COM Port [", SERIALPORT, "] not found, exiting..."
190+
exitNow(1)
191+
192+
try:
193+
if not 0<TARGET<= 255:
194+
print "TARGET not provided (use -h for help), now exiting..."
195+
exit(1)
196+
197+
#send target ID first
198+
if waitForTargetSet(TARGET):
199+
print "TARGET SET OK"
200+
else:
201+
print "TARGET SET FAIL, exiting..."
202+
exit(1)
203+
204+
with open(HEX) as f:
205+
print "File found, passing to Moteino..."
206+
207+
handshakeResponse = waitForHandshake()
208+
if (handshakeResponse == HANDSHAKE_OK):
209+
seq = 0
210+
packetCounter = 0
211+
content = f.readlines()
212+
213+
while seq < len(content):
214+
#{
215+
currentLine = content[seq].strip()
216+
isEOF = (content[seq].strip() == ":00000001FF") #this should be the last line in any valid intel HEX file
217+
result = -1
218+
bundledLines = 1
219+
220+
if isEOF==False:
221+
#{
222+
hexDataToSend = currentLine
223+
224+
#bundle 2 or 3 HEX lines in 1 RF packet (assuming: 1 HEX line has 16 bytes of data, following HEX lines also each being 16 bytes)
225+
if LINESPERPACKET > 1 and currentLine[1:3] == '10' and len(currentLine)==43:
226+
#{
227+
#check if next line != EOF, so we can bundle 2 lines
228+
nextLine = content[seq+1].strip()
229+
if nextLine != ":00000001FF" and nextLine[1:3] == "10" and len(nextLine) == 43:
230+
#{
231+
#need to sum: the 2 lines checksums + address bytes of nextLine (to arrive at a correct final checksum of combined 2 lines
232+
checksum = int(currentLine[41:43], 16) + int(nextLine[41:43], 16) + int(nextLine[3:5], 16) + int(nextLine[5:7], 16)
233+
234+
#check if a third line != EOF, so we can bundle 3 lines
235+
nextLine2 = content[seq+2].strip()
236+
if LINESPERPACKET==3 and nextLine2 != ":00000001FF" and nextLine2[1:3] == "10" and len(nextLine2) == 43:
237+
#{
238+
#need to sum: the previous checksum + address bytes of nextLine2 (to arrive at a correct final checksum of combined 3 lines
239+
checksum += int(nextLine2[41:43], 16) + int(nextLine2[3:5], 16) + int(nextLine2[5:7], 16)
240+
hexDataToSend = ":3" + hexDataToSend[2:(len(hexDataToSend)-2)] + nextLine[9:41] + nextLine2[9:41] + ('%0*X' % (2,checksum%256))
241+
bundledLines=3
242+
#}
243+
else:
244+
#{
245+
hexDataToSend = ":2" + hexDataToSend[2:(len(hexDataToSend)-2)] + nextLine[9:41] + ('%0*X' % (2,checksum%256))
246+
bundledLines=2;
247+
#}
248+
#}
249+
#}
250+
tx = "FLX:" + str(packetCounter) + hexDataToSend
251+
print "TX > " + tx
252+
serWriteln(ser, tx)
253+
result = waitForSEQ(packetCounter)
254+
#}
255+
elif waitForHandshake(True) == HANDSHAKE_OK:
256+
#{
257+
print "SUCCESS! (time elapsed: " + ("%.2f" % ((millis()-start)/1000.0)) + "s)"
258+
exit(0);
259+
#}
260+
else:
261+
#{
262+
print "FAIL, IMG REFUSED BY TARGET (size exceeded? verify target MCU matches compiled target)"
263+
exit(99)
264+
#}
265+
266+
if result == 1:
267+
#{
268+
seq+=bundledLines
269+
packetCounter+=1
270+
#}
271+
elif result == 2: # out of synch, retry
272+
#{
273+
if retries > 0:
274+
retries-=1
275+
print "OUT OF SYNC: retrying...\n"
276+
continue
277+
else:
278+
print "FAIL: out of sync (are you running the latest OTA libs/sources?)"
279+
exit(1)
280+
#}
281+
else:
282+
#{
283+
if retries > 0:
284+
retries-=1
285+
print "Timeout, retry...\n"
286+
continue
287+
else:
288+
#{
289+
print "FAIL: timeout (are you running the latest OTA libs/sources?)"
290+
exit(1)
291+
#}
292+
#}
293+
#}
294+
295+
while 1:
296+
#{
297+
rx = ser.readline()
298+
if (len(rx) > 0): print rx.strip()
299+
#}
300+
301+
elif (handshakeResponse == HANDSHAKE_FAIL_TIMEOUT):
302+
print "FAIL: No response from Moteino programmer, is it connected to " + port
303+
exit(1)
304+
elif (handshakeResponse == HANDSHAKE_FAIL_TIMEOUT):
305+
print "FAIL: No response from Moteino programmer, is it connected to " + port
306+
exit(1)
307+
else:
308+
print "FAIL: No response from Moteino Target, is Target listening on same Freq/NetworkID & OTA enabled?"
309+
exit(1)
310+
311+
except IOError:
312+
print "File [", HEX, "] not found, exiting..."
313+
exit(1)
314+
315+
finally:
316+
#print 'FINALLY' + '\n'
317+
ser.close()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Wireless Programming for Moteinos
2+
3+
## How to use
4+
Since v1.5 you can now run this app in several ways:
5+
- natively via the WirelessProgramming.exe GUI app
6+
- the windows GUI can also invoke the OTA.py script via embedded IronPython engine (parameters from GUI pass to the OTA.py script)
7+
- cross platform straight from Python (2.7 runtime) by supplying parameters (run `python OTA.py -h` for details)
8+
<br/>
9+
Make sure to download the entire repo ZIP, not individual files.
Binary file not shown.
2.05 MB
Binary file not shown.

0 commit comments

Comments
 (0)