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