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 ()
0 commit comments