1
2#!/usr/bin/env python
3# -*- encoding: utf-8 -*-
4'''
5@File       :    haasdevice.py
6@Description:    file description
7@Date       :    2021/06/03 10:03:12
8@Author     :    guoliang.wgl
9@version    :    1.0
10'''
11
12# here put the import lib
13
14import time
15import os
16import logging
17import serial
18import logging
19from subprocess import *
20import threading
21import time
22import sys
23import argparse
24import socket
25import json
26
27logging.basicConfig(level=logging.INFO,
28                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
29
30
31class HaaSboard:
32    def __init__(self, device, baudrate=115200, user="micro", password="python", wait=0):
33        self.serial = serial.Serial()
34        self.serial.port = device
35        self.serial.baudrate = baudrate
36        self.serial.parity = "N"
37        self.serial.bytesize = 8
38        self.serial.stopbits = 1
39        self.serial.timeout = 0.05
40
41        try:
42            self.serial.open()
43        except Exception as e:
44            raise Exception("Failed to open serial port: %s!" % device)
45
46
47
48    def close(self):
49        self.serial.close()
50
51
52    def run_cmd(self,cmd):
53        self.serial.write(cmd)
54
55    def run_cmd_follow(self,cmd,ending,timeout = 10):
56        self.serial.write(cmd)
57        return self.read_until(1,ending,timeout)
58
59
60
61    def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None):
62        # if data_consumer is used then data is not accumulated and the ending must be 1 byte long
63        assert data_consumer is None or len(ending) == 1
64
65        data = self.serial.read(min_num_bytes)
66        if data_consumer:
67            data_consumer(data)
68        timeout_count = 0
69        while True:
70            if data.endswith(ending):
71                break
72            elif self.serial.inWaiting() > 0:
73                new_data = self.serial.read(1)
74                if data_consumer:
75                    data_consumer(new_data)
76                    data = new_data
77                else:
78                    data = data + new_data
79                timeout_count = 0
80            else:
81                timeout_count += 1
82                if timeout is not None and timeout_count >= 100 * timeout:
83                    logging.warning("Waiting for  %s  timeout" %(ending.decode('utf-8')))
84                    break
85                time.sleep(0.01)
86        return data
87
88
89class HaaSDevice(object):
90    """
91    all the HaaSDevice  functions can be found int this class
92    """
93
94    CMD_REBOOT = 'reboot \n'
95    FLAG_REBOOT = 'register python command succeed'
96    CMD_UNZIP = 'unzip'
97    FLAG_UNZIP = 'register python command succeed'
98    CMD_WIFI = "python /data/python-apps/wifi/main.py "
99    FLAG_WIFI = "DeviceIP:"
100
101    CMD_TFTP = "tftp get "
102    FLAG_TFTP = "tftp received"
103
104
105
106
107    def __init__(self):
108        self._baudrate = 115200
109        self._dev_node = None
110        self._board = None
111        self._ip = None
112        self._ssid = None
113        self._pwd = None
114
115    def is_str_empty(self,value):
116        if value is None or value == "" :
117            return True
118        else:
119            return False
120
121    @property
122    def baudrate(self):
123        return self._baudrate
124
125    @baudrate.setter
126    def baudrate(self,value):
127        self._baudrate = value
128
129
130    @property
131    def dev_node(self):
132        return self._dev_node
133
134    @dev_node.setter
135    def dev_node(self,value):
136        self._dev_node = value
137
138
139    @property
140    def ip(self):
141        return self._ip
142
143    @property
144    def ssid(self):
145        return self._ssid
146
147    @ssid.setter
148    def ssid(self,value):
149        self._ssid = value
150
151
152    @property
153    def pwd(self):
154        return self._pwd
155
156    @ssid.setter
157    def pwd(self,value):
158        self._pwd = value
159
160
161    def connect_board(self):
162         self._board = HaaSboard(self._dev_node,self._baudrate,wait=5)
163
164    def reboot(self):
165        if self._board is None:
166            raise OSError("Please connect the board  first")
167        logging.info("Try to reboot device...")
168
169        ret = self._board.run_cmd_follow(self.CMD_REBOOT.encode('utf-8'),self.FLAG_REBOOT.encode('utf-8'))
170        if ret :
171            logging.info("reboot succeed ")
172        else :
173            raise OSError("reboot failed ")
174
175    def connect_wifi(self):
176        """
177        todo : verify if the wifi is connected
178        """
179        if self._board is None:
180            raise OSError("Please connect the board  first")
181
182        if self.is_str_empty(self._ssid):
183            raise ValueError("Error:ssid is null")
184
185        if self.is_str_empty(self._pwd):
186            raise ValueError("Error:password is null")
187
188
189        cmd =  self.CMD_WIFI+ ' ' + self._ssid + ' ' + self._pwd + ' ' + '\r\n'
190        logging.info("connect_wifi:" +cmd)
191        ret = self._board.run_cmd_follow(cmd.encode('utf-8'),self.FLAG_WIFI.encode('utf-8'),10)
192
193        if ret :
194            pass
195        else:
196            raise("Connect wifi failed")
197
198        self._ip = self._board.read_until(1,b"\n").decode('UTF-8').strip()
199        self._board.read_until(1,b"ConnectWifi finished",3)
200        return self._ip
201
202
203    def tftp_get(self,src_ip,port,src_file,dest = None):
204        if self._board is None:
205            raise OSError("Please connect the board  first")
206
207        if self.is_str_empty(src_ip):
208            raise ValueError("src IP can't be None")
209
210        if self.is_str_empty(src_file):
211            raise ValueError("src file can't be None")
212
213        if not os.access(src_file,os.R_OK):
214            raise PermissionError("src file can't be read")
215
216        if self.is_str_empty(dest):
217            dest = '/data/' + src_file
218        else:
219            dest = dest + src_file
220
221        if src_file.endswith('.zip'):
222            tftp_get_cmd = self.CMD_TFTP + ' ' + src_ip + ' ' + port + ' ' + src_file  + ' ' +  dest +  ' ' + 'binary' +  '\n'
223        else:
224            tftp_get_cmd = self.CMD_TFTP + ' ' + src_ip + ' ' + port + ' ' + src_file  + ' ' +  dest +   ' ' +  'text' +  '\n'
225
226
227        self._board.run_cmd_follow(tftp_get_cmd.encode('utf-8'),b"tftp received")
228        ret = self._board.read_until(1,b"\n").decode('UTF-8').strip()
229        if 'failed' in ret:
230            logging.error('run '+ tftp_get_cmd + ' failed:tftp received ' + ret )
231            return None
232        else:
233            return dest
234
235    def unzip(self,src,dest = None):
236        if self._board is None:
237            raise OSError("Please connect the board  first")
238
239        if self.is_str_empty(dest):
240            dest = '/data/'
241
242        dest = dest + 'app' + '/'
243
244        # 创建目录
245        mk_dir = 'mkdir ' + dest + ' \n'
246        self._board.run_cmd(mk_dir.encode('utf-8'))
247        time.sleep(1)
248        unzip_cmd =  'unzip '   + src + ' ' + dest  +  '\n'
249        logging.info("unzip cmd:" +unzip_cmd)
250        ret = self._board.run_cmd_follow(unzip_cmd.encode('utf-8'),b"unzip succeed")
251        return dest+'main.py'
252
253
254    def run_app(self,main_entry):
255        if self._board is None:
256            raise OSError("Please connect the board  first")
257
258        if self.is_str_empty(main_entry):
259            raise ValueError("app entry:" + main_entry + "cat't be null")
260
261        run_cmd  = 'python ' + main_entry + '\r\n'
262        logging.info("Try to run app:" + main_entry)
263
264        self._board.run_cmd_follow(run_cmd.encode('utf-8'), '\r\n'.encode('utf-8'))
265
266
267    def close(self):
268
269        if self._board is None:
270            raise OSError("Please connect the board  first")
271        else:
272            self._board.close()
273
274def start_client_and_run(dev_node,baudrate,ssid,pwd,host,port,srcfile,dest_dir):
275    """
276    @description  :
277    ---------
278    @param        :
279    -------
280    @Returns      :
281    -------
282    """
283    device = HaaSDevice()
284
285
286    device.baudrate = baudrate
287    device.dev_node = dev_node
288    device.ssid = ssid
289    device.pwd = pwd
290
291    device.connect_board()
292    #device.reboot()
293
294    if device.ip is None:
295        try:
296            logging.info("Try to connect wifi for device first time")
297            device.connect_wifi()
298        except SystemError:
299            print('')
300
301    if device.ip is None:
302        logging.info("Try to connect wifi for device second time")
303        device.connect_wifi()
304
305    if device.ip is None:
306        raise OSError("Connect wifi failed ,no ip found")
307
308    logging.info("GetDevice IP succeed: " + device.ip)
309    #todo get pc ip
310    #ping pc and device
311    pc_ip = host
312    src_file = srcfile
313    tftp_port = port
314    if 'sdcard' in dest_dir:
315        dest_dir = '/sdcard/'
316    else:
317        dest_dir = '/data/'
318
319    ret = device.tftp_get(pc_ip,tftp_port,src_file,dest_dir)
320    if ret:
321        logging.info("Push files to device succeed: " + ret)
322        if src_file.endswith('.zip'):
323            py_main = device.unzip(ret,dest = dest_dir)
324            logging.info("unzip files to device succeed: " + py_main)
325        elif src_file.endswith('.py'):
326            py_main = dest_dir + src_file
327        else:
328            py_main = None
329
330    else:
331        raise OSError("tftp_get   failed" )
332
333    if not is_str_empty(py_main):
334        device.run_app(py_main)
335        logging.info("run "  + py_main + " succeed" )
336
337    device.close()
338    return True
339
340
341def start_tftp_server(host,post):
342    global event
343    global on_server_started
344    logging.info("Trying to start tftp server...")
345    tftp_info = Popen(["py3tftp", "--host", host,"-p",port],stdout = PIPE,stderr = STDOUT)
346
347    retry = 0
348    while tftp_info.poll() is None:
349        retry = retry + 1
350        info = tftp_info.stdout.readline().decode('utf-8')
351        if "Listening..." in info:
352            on_server_started = True
353            break
354        if (retry > 10 ):
355            logging.error("Start tftp server failed")
356            break
357
358        logging.info(info)
359
360    if on_server_started:
361        event.wait()
362        tftp_info.kill()
363    else:
364        raise SystemError("tftp server start failed,by retry 10 times")
365
366
367
368
369
370
371def stop_tftp_server():
372    global event
373    event.set()
374
375
376def is_str_empty(value):
377    if value is None or value == "" :
378        return True
379    else:
380        return False
381
382if __name__ == '__main__':
383    on_server_started = False
384    logging.info("main function start ")
385
386    if(len(sys.argv) == 2):
387        config = sys.argv[1]
388        logging.info("tftp config  is : %s " % config )
389        if not os.path.isfile(config):
390            logging.error("tftp config must be a file")
391            sys.exit(1)
392
393        with open(config,'r') as f :
394            json_str = json.load(f)
395            logging.info(json_str)
396
397
398            # 以下是必选项
399            host = json_str['ip']
400            port = '6069'
401            baudrate = json_str['baudrate']
402            dev_node = json_str['serialPort']
403            ssid = json_str['ssid']
404            pwd = json_str['pwd']
405            dest_dir = json_str['dstDir']
406            if 'file' in json_str:
407                target_file = json_str['file']
408            else:
409                target_file = 'app.zip'
410    else:
411
412        cmd_parser = argparse.ArgumentParser()
413        cmd_parser.add_argument('-d', '--device', default='', help='the serial device ')
414        cmd_parser.add_argument('-b', '--baudrate', default='', help='the baud rate of the serial device')
415        cmd_parser.add_argument('-s', '--ssid', default='', help='the wifi ssid')
416        cmd_parser.add_argument('-p', '--password', default='', help='the wifi password')
417        cmd_parser.add_argument('-ip', '--ip', default='', help='the PC ip address')
418        cmd_parser.add_argument('-f', '--file', default='', help='the file to be send')
419        cmd_parser.add_argument('--dest', default='', help='the dest dir file to be send')
420        args = cmd_parser.parse_args()
421        host = args.ip
422        port = '6069'
423
424        baudrate = args.baudrate
425        dev_node = args.device
426        ssid = args.ssid
427        pwd = args.password
428
429        if is_str_empty(args.file):
430            targetfile =  'app.zip'
431        else:
432            targetfile = args.file
433
434
435    # host = '192.168.3.241'
436    # port = '6069'
437
438    # baudrate = 1500000
439    # dev_node = "/dev/cu.SLAB_USBtoUART"
440    # ssid = "KIDS"
441    # pwd = "12345678"
442
443
444    # host = socket.gethostbyname(socket.gethostname())
445
446
447    if is_str_empty(host) or is_str_empty(baudrate) or is_str_empty(dev_node) or is_str_empty(ssid) or is_str_empty(pwd):
448        #logging.error("Usage1:python  haasdevice.py -d /dev/cu.SLAB_USBtoUART -b 1500000 -s KIDS -p 12345678 -ip 192.168.3.241")
449        logging.error("Usage2:python  haasdevice.py tftpcfg.json")
450        sys.exit(1)
451
452
453
454    event = threading.Event()
455    event.clear()
456
457    server_t = threading.Thread(target=start_tftp_server,args=((host,port)))
458    server_t.start()
459
460    while ( not on_server_started):
461        time.sleep(1)
462        logging.info("Waiting for tftp server started....")
463    else:
464        logging.info("tftp server start succeed")
465        start_client_and_run(dev_node,baudrate,ssid,pwd,host,port,target_file,dest_dir)
466        stop_tftp_server()
467