simplify imports
[fpv.git] / microb_cmd / microbcmd.py
1 #! /usr/bin/env python
2
3 import os,sys,termios,atexit
4 import serial
5 from select import select
6 import cmd
7 #import pylab
8 from  matplotlib import pylab
9 from math import *
10
11 import struct
12 import numpy
13 import shlex
14 import time
15 import math
16 import warnings
17 warnings.filterwarnings("ignore","tempnam",RuntimeWarning, __name__)
18
19 import logging
20 log = logging.getLogger("MicrobShell")
21 _handler = logging.StreamHandler()
22 _handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
23 log.addHandler(_handler)
24 log.setLevel(1)
25
26 MICROB_PATH=os.path.dirname(sys.argv[0])
27
28 SPM_PAGE_SIZE = 256
29 METADATA_ADDR = 256
30
31 def crc_ccitt_update (crc, data):
32     """crc argument is the previous value of 16 bits crc (the initial
33     value is 0xffff). 'data' is the 8 bits value added to crc. The
34     function returns the new crc value."""
35
36     data ^= (crc & 0xff)
37     data ^= (data << 4)
38     data &= 0xff
39
40     ret = (data << 8) & 0xffff
41     ret |= ((crc >> 8) & 0xff)
42     ret ^= ((data >> 4) & 0xff)
43     ret ^= ((data << 3) & 0xffff)
44     return ret
45
46 def do_crc(buf):
47     i = 0
48     crc = 0xffff
49     sum = 0
50     while i < len(buf):
51         crc = crc_ccitt_update(crc, ord(buf[i]))
52         sum +=  ord(buf[i])
53         i += 1
54     return (crc << 16) + (sum & 0xffff)
55
56 def prog_page(ser, addr, buf):
57     """program a page from buf at addr"""
58
59     # switch in program mode
60     ser.flushInput()
61     ser.write('p')
62
63     # send address
64     s = ser.readline()
65     if not s.endswith("addr?\r\n"):
66         print "failed (don't match addr)"
67         return -1
68     ser.write("%x\n"%addr)
69     s = ser.readline()
70     if not s.startswith("ok"):
71         print "failed"
72         return -1
73
74     # fill page with buf data
75     page = [ '\xff' ] * SPM_PAGE_SIZE
76     i = 0
77     while i < SPM_PAGE_SIZE and i < len(buf):
78         page[i] = buf[i]
79         i += 1
80
81     # send data
82     i = 0
83     while i < SPM_PAGE_SIZE:
84         c = page[i]
85         ser.write(c)
86         i += 1
87
88     sys.stdout.write(".")
89     sys.stdout.flush()
90
91     # compare crc
92     avr_crc = int(ser.readline()[0:8], 16)
93
94     crc = do_crc(page)
95     if crc != avr_crc:
96         print "failed: bad crc %x %x"%(crc, avr_crc)
97         ser.write('n')
98         return -1
99
100     ser.write('y')
101     s = ser.readline()
102     if not s.startswith("OK"):
103         print "failed"
104         return -1
105     return 0
106
107 def prog_metadata(ser, addr, buf):
108     length = len(buf)
109     crc = do_crc(buf)
110     page = struct.pack(">LL", length, crc)
111     filename = os.path.join(MICROB_PATH, "binaries/%x_%x.bin"%(length, crc))
112     print "saving in %s"%(filename)
113     f = open(filename, "w")
114     f.write(buf)
115     f.close()
116     return prog_page(ser, addr, page)
117
118 def get_same_bin_file(ser):
119     l = read32(ser, METADATA_ADDR)
120     c = read32(ser, METADATA_ADDR + 4)
121     filename = os.path.join(MICROB_PATH,
122                             "binaries/%x_%x.bin"%(l, c))
123     print filename
124     try:
125         f = open(filename, "r")
126     except:
127         return None
128     buf = f.read()
129     f.close()
130     print "found old bin matching <%s>"%(filename)
131     return buf
132
133 def read32(ser, addr):
134     """read a 32 bits value at addr"""
135
136     ser.write('\n')
137     ser.write('\n')
138     ser.write('\n')
139     time.sleep(0.2)
140     ser.flushInput()
141
142     # switch in program mode
143     ser.write('d')
144
145     # send address
146     time.sleep(0.1)
147
148     s = ser.readline()
149     print repr(s)
150     if not s.endswith("addr?\r\n"):
151         print "failed (don't match addr)"
152         return -1
153     print addr
154     ser.write("%x\n"%addr)
155     s = ser.readline()
156     print repr(s)
157     return int(s, 16)
158
159 def check_crc(ser, buf, offset, size):
160     """Process the crc of buf, ask for a crc of the flash, and check
161     that value is correct"""
162     if size <= 0:
163         return 0
164
165     # go in CRC mode
166     ser.flushInput()
167     ser.write('c')
168
169     # send addr
170     s = ser.readline()
171     if not s.endswith("addr?\r\n"):
172         print "failed <%s>"%s
173         return -1
174     ser.write("%x\n"%offset)
175
176     # send size
177     s = ser.readline()
178     if not s.startswith("size?"):
179         print "failed"
180         return -1
181     ser.write("%x\n"%size)
182
183     # compare CRC
184     crc = do_crc(buf[offset:offset+size])
185     avr_crc = int(ser.readline()[0:8], 16)
186     if crc != avr_crc:
187         return -1
188     return 0
189
190 class SerialLogger:
191     def __init__(self, ser, filein, fileout=None):
192         self.ser = ser
193         self.filein = filein
194         self.fin = open(filein, "a", 0)
195         if fileout:
196             self.fileout = fileout
197             self.fout = open(fileout, "a", 0)
198         else:
199             self.fileout = filein
200             self.fout = self.fin
201     def fileno(self):
202         return self.ser.fileno()
203     def read(self, *args):
204         res = self.ser.read(*args)
205         self.fin.write(res)
206         return res
207     def write(self, s):
208         self.fout.write(s)
209         self.ser.write(s)
210
211 class Interp(cmd.Cmd):
212     prompt = "Microb> "
213     def __init__(self, tty, baudrate=57600):
214         cmd.Cmd.__init__(self)
215         self.ser = serial.Serial(tty,baudrate=baudrate)
216         self.escape  = "\x01" # C-a
217         self.quitraw = "\x02" # C-b
218         self.serial_logging = False
219         self.default_in_log_file = "/tmp/microb.in.log"
220         self.default_out_log_file = "/tmp/microb.out.log"
221
222     def do_quit(self, args):
223         return True
224
225     def do_log(self, args):
226         """Activate serial logs.
227         log <filename>           logs input and output to <filename>
228         log <filein> <fileout>   logs input to <filein> and output to <fileout>
229         log                      logs to /tmp/microb.log or the last used file"""
230
231         if self.serial_logging:
232             log.error("Already logging to %s and %s" % (self.ser.filein,
233                                                         self.ser.fileout))
234         else:
235             self.serial_logging = True
236             files = [os.path.expanduser(x) for x in args.split()]
237             if len(files) == 0:
238                 files = [self.default_in_log_file, self.default_out_log_file]
239             elif len(files) == 1:
240                 self.default_in_log_file = files[0]
241                 self.default_out_log_file = None
242             elif len(files) == 2:
243                 self.default_in_log_file = files[0]
244                 self.default_out_log_file = files[1]
245             else:
246                 print "Can't parse arguments"
247
248             self.ser = SerialLogger(self.ser, *files)
249             log.info("Starting serial logging to %s and %s" % (self.ser.filein,
250                                                                self.ser.fileout))
251
252
253     def do_unlog(self, args):
254         if self.serial_logging:
255             log.info("Stopping serial logging to %s and %s" % (self.ser.filein,
256                                                                self.ser.fileout))
257             self.ser = self.ser.ser
258             self.serial_logging = False
259         else:
260             log.error("No log to stop")
261
262
263     def do_raw(self, args):
264         "Switch to RAW mode"
265         stdin = os.open("/dev/stdin",os.O_RDONLY)
266         stdout = os.open("/dev/stdout",os.O_WRONLY)
267
268         stdin_termios = termios.tcgetattr(stdin)
269         raw_termios = stdin_termios[:]
270
271         try:
272             log.info("Switching to RAW mode")
273
274             # iflag
275             raw_termios[0] &= ~(termios.IGNBRK | termios.BRKINT |
276                                 termios.PARMRK | termios.ISTRIP |
277                                 termios.INLCR | termios.IGNCR |
278                                 termios.ICRNL | termios.IXON)
279             # oflag
280             raw_termios[1] &= ~termios.OPOST;
281             # cflag
282             raw_termios[2] &= ~(termios.CSIZE | termios.PARENB);
283             raw_termios[2] |= termios.CS8;
284             # lflag
285             raw_termios[3] &= ~(termios.ECHO | termios.ECHONL |
286                                 termios.ICANON | termios.ISIG |
287                                 termios.IEXTEN);
288
289             termios.tcsetattr(stdin, termios.TCSADRAIN, raw_termios)
290
291             mode = "normal"
292             while True:
293                 ins,outs,errs=select([stdin,self.ser],[],[])
294                 for x in ins:
295                     if x == stdin:
296                         c = os.read(stdin,1)
297                         if mode  == "escape":
298                             mode =="normal"
299                             if c == self.escape:
300                                 self.ser.write(self.escape)
301                             elif c == self.quitraw:
302                                 return
303                             else:
304                                 self.ser.write(self.escape)
305                                 self.ser.write(c)
306                         else:
307                             if c == self.escape:
308                                 mode = "escape"
309                             else:
310                                 self.ser.write(c)
311                     elif x == self.ser:
312                         os.write(stdout,self.ser.read())
313         finally:
314             termios.tcsetattr(stdin, termios.TCSADRAIN, stdin_termios)
315             log.info("Back to normal mode")
316
317     def bootloader(self, filename, boardnum):
318         self.ser.write("\ 3")
319         time.sleep(0.4)
320         self.ser.write("bootloader\n")
321         time.sleep(0.4)
322         self.ser.write("\n")
323
324         print "start programming"
325         self.ser.flushInput()
326         f = open(filename)
327         buf = f.read()
328         f.close()
329
330         addr = 0
331
332         old_buf = get_same_bin_file(self.ser)
333         #old_buf = None
334
335         while addr < len(buf):
336             if addr > METADATA_ADDR and old_buf != None and \
337                     old_buf[addr:addr+SPM_PAGE_SIZE] == buf[addr:addr+SPM_PAGE_SIZE]:
338                 sys.stdout.write("-")
339                 sys.stdout.flush()
340                 addr += SPM_PAGE_SIZE
341                 continue
342             time.sleep(0.1)
343             if check_crc(self.ser, buf, addr, SPM_PAGE_SIZE) == 0:
344                 sys.stdout.write("*")
345                 sys.stdout.flush()
346             elif prog_page(self.ser, addr,
347                          buf[addr:addr+SPM_PAGE_SIZE]) != 0:
348                 return
349             addr += SPM_PAGE_SIZE
350         if check_crc(self.ser, buf, 0, len(buf)):
351             print "crc failed"
352             return
353         print
354         if prog_metadata(self.ser, METADATA_ADDR, buf) != 0:
355             print "metadata failed"
356             return
357         print "Done."
358         self.ser.write("x")
359         self.do_raw("")
360
361     def do_bootloader(self, args):
362         self.bootloader(args, 0)
363
364
365 if __name__ == "__main__":
366     try:
367         import readline,atexit
368     except ImportError:
369         pass
370     else:
371         histfile = os.path.join(os.environ["HOME"], ".microb_history")
372         atexit.register(readline.write_history_file, histfile)
373         try:
374             readline.read_history_file(histfile)
375         except IOError:
376             pass
377
378     device = "/dev/ttyS0"
379     if len(sys.argv) > 1:
380         device = sys.argv[1]
381     interp = Interp(device)
382     while 1:
383         try:
384             interp.cmdloop()
385         except KeyboardInterrupt:
386             print
387         except Exception,e:
388             l = str(e).strip()
389             if l:
390                 log.exception("%s" % l.splitlines()[-1])
391             continue
392         break