commit 967dc50e248c209d62f290fbcd9c723e7d73be78 Author: chenchi Date: Thu Mar 3 09:53:51 2022 +0800 initial commit diff --git a/code/alert.py b/code/alert.py new file mode 100644 index 0000000..18270a1 --- /dev/null +++ b/code/alert.py @@ -0,0 +1,6 @@ + +class AlertMonitor(object): + ''' + Recv alert signals and process them + ''' + pass \ No newline at end of file diff --git a/code/battery.py b/code/battery.py new file mode 100644 index 0000000..012d41c --- /dev/null +++ b/code/battery.py @@ -0,0 +1,12 @@ + +class Battery(object): + def __init__(self): + pass + + def indicate(self, low_power_threshold, low_power_cb): + self.low_power_threshold = low_power_threshold + self.low_power_cb = low_power_cb + pass + + def charge(self): + pass \ No newline at end of file diff --git a/code/dev_info.py b/code/dev_info.py new file mode 100644 index 0000000..5776465 --- /dev/null +++ b/code/dev_info.py @@ -0,0 +1,7 @@ + +version = '1.0.0' + +quecIot = { + 'PK': 'p1122c', + 'PS': 'UG9wMGxwS2t1UE5x' +} diff --git a/code/led.py b/code/led.py new file mode 100644 index 0000000..4fab2c0 --- /dev/null +++ b/code/led.py @@ -0,0 +1,4 @@ + +class LED(): + def __init__(self): + pass \ No newline at end of file diff --git a/code/location.py b/code/location.py new file mode 100644 index 0000000..9135885 --- /dev/null +++ b/code/location.py @@ -0,0 +1,157 @@ + +import usr.settings as settings +from machine import UART +import cellLocator +import wifilocator +import ure +import _thread +from queue import Queue + +gps_data_retrieve_queue = None + +''' +GPS data retrieve callback from UART +When data comes, send a message to queue of data length +''' +def gps_data_retrieve_cb(para_list): + global gps_data_retrieve_queue + toRead = para_list[2] + if toRead: + gps_data_retrieve_queue.put(toRead) + +''' +GPS data retrieve thread +Receive a message from queue of data length. +Then read the corresponding length of data from UART into self.gps_data. +So self.gps_data will be updated immediately once the data comes to UART that +the self.gps_data could keep the latest data. +''' +def gps_data_retrieve_thread(argv): + global gps_data_retrieve_queue + self = argv + + while True: + toRead = gps_data_retrieve_queue.get() + if toRead: + self.gps_data = self.uart_read(toRead).decode() + +class GPS(UART): + def __init__(self, gps_cfg): + global gps_data_retrieve_queue + super(GPS, self).__init__(gps_cfg['UARTn'], gps_cfg['buadrate'], gps_cfg['databits'], gps_cfg['parity'], gps_cfg['stopbits'], gps_cfg['flowctl']) + self.set_callback(gps_data_retrieve_cb) + self.gps_data = '' + gps_data_retrieve_queue = Queue(maxsize=8) + _thread.start_new_thread(gps_data_retrieve_thread, (self,)) + + def uart_read(self, nread): + return super(GPS, self).read(nread).decode() + + def read(self): + return self.gps_data + + def read_location_GxRMC(self): + gps_data = self.read() + rmc_re = ure.search( + r"\$G[NP]RMC,[0-9]+\.[0-9]+,A,[0-9]+\.[0-9]+,[NS],[0-9]+\.[0-9]+,[EW],[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[0-9]+,,,[ADE],[SCUV]\*[0-9]+", + gps_data) + if rmc_re: + return rmc_re.group(0) + else: + return "" + + def read_location_GxGGA(self): + gps_data = self.read() + gga_re = ure.search( + r"\$G[BLPN]GGA,[0-9]+\.[0-9]+,[0-9]+\.[0-9]+,[NS],[0-9]+\.[0-9]+,[EW],[126],[0-9]+,[0-9]+\.[0-9]+,-*[0-9]+\.[0-9]+,M,-*[0-9]+\.[0-9]+,M,,\*[0-9]+", + gps_data) + if gga_re: + return gga_re.group(0) + else: + return "" + +class CellLocator(cellLocator): + def __init__(self, cellLocator_cfg): + self.cellLocator_cfg = cellLocator_cfg + + def read(self): + return super(CellLocator, self).getLocation(self.cellLocator_cfg['serverAddr'], self.cellLocator_cfg['port'], self.cellLocator_cfg['token'], self.cellLocator_cfg['timeout'], self.cellLocator_cfg['profileIdx']) + +class WiFiLocator(wifilocator): + def __init__(self, wifiLocator_cfg): + super(WiFiLocator, self).__init__(wifiLocator_cfg['token']) + + def read(self): + return super(WiFiLocator, self).getwifilocator() + +def loc_worker(argv): + self = argv + while True: + trigger = self.trigger_queue.get() + if trigger: + data = self.read() + if data and self.read_cb: + self.read_cb(data) + +class Location(GPS, CellLocator, WiFiLocator): + gps_enabled = False + cellLoc_enabled = False + wifiLoc_enabled = False + + def __init__(self, read_cb, **kw): + current_settings = settings.get() + + self.read_cb = read_cb + + if current_settings['app']['loc_method'] & settings.default_values_app._loc_method.gps: + if 'gps_cfg' in kw: + super(Location, self).__init__(kw['gps_cfg']) + self.gps_enabled = True + else: + raise ValueError('Invalid gps init parameters.') + + if current_settings['app']['loc_method'] & settings.default_values_app._loc_method.cell: + if 'cellLocator_cfg' in kw: + super(GPS, self).__init__(kw['cellLocator_cfg']) + self.cellLoc_enabled = True + else: + raise ValueError('Invalid cell-locator init parameters.') + + if current_settings['app']['loc_method'] & settings.default_values_app._loc_method.wifi: + if 'wifiLocator_cfg' in kw: + super(CellLocator, self).__init__(kw['wifiLocator_cfg']) + self.wifiLoc_enabled = True + else: + raise ValueError('Invalid wifi-locator init parameters.') + + self.trigger_queue = Queue(maxsize=64) + _thread.start_new_thread(loc_worker, (self,)) + + def read(self): + if self.gps_enabled: + data = [] + r = super(Location, self).read_location_GxRMC() + if r: + data.append(r) + + r = super(Location, self).read_location_GxGGA() + if r: + data.append(r) + + if len(data): + return (settings.default_values_app._loc_method.gps, data) + + if self.cellLoc_enabled: + data = super(GPS, self).read() + if data: + return (settings.default_values_app._loc_method.cell, data) + + if self.wifiLoc_enabled: + data = super(CellLocator, self).read() + if data: + return (settings.default_values_app._loc_method.wifi, data) + + return () + + def trigger(self): + self.trigger_queue.put(True) diff --git a/code/logging.py b/code/logging.py new file mode 100644 index 0000000..31b3d94 --- /dev/null +++ b/code/logging.py @@ -0,0 +1,56 @@ + +import utime + +def asyncLog(name, level, *message, timeout=None, await_connection=True): + + + ''' + pass + # + # yield config.getMQTT().publish(base_topic.format(level), message, qos=1, timeout=timeout, + # await_connection=await_connection) + ''' + pass + +def log(name, level, *message, local_only=False, return_only=False, timeout=None): + + if hasattr(utime, "strftime"): + print("[{}]".format(utime.strftime("%Y-%m-%d %H:%M:%S")), "[{}]".format(name), + "[{}]".format(level), *message) + else: + t = utime.localtime() + print("[{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}]".format(*t), "[{}]".format(name), + "[{}]".format(level), *message) + if return_only: + return + if not local_only: + pass + +class Logger: + def __init__(self, name): + self.name = name + + def critical(self, *message, local_only=True): + log(self.name, "critical", *message, local_only=local_only, timeout=None) + + def error(self, *message, local_only=True): + log(self.name, "error", *message, local_only=local_only, timeout=None) + + def warn(self, *message, local_only=True): + log(self.name, "warn", *message, local_only=local_only, timeout=None) + + def info(self, *message, local_only=True): + log(self.name, "info", *message, local_only=local_only, timeout=20) + + def debug(self, *message, local_only=True): + log(self.name, "debug", *message, local_only=local_only, timeout=5) + + def asyncLog(self, level, *message, timeout=True): + log(self.name, level, *message, return_only=True) + if timeout == 0: + return + asyncLog(self.name, level, *message, timeout=timeout) + + +def getLogger(name): + return Logger(name) diff --git a/code/main.py b/code/main.py new file mode 100644 index 0000000..49ef65e --- /dev/null +++ b/code/main.py @@ -0,0 +1,62 @@ + +import usr.settings as settings +from usr.tracker import Tracker +from usr.logging import getLogger +import UART +import osTimer + +tracker = None + +log = getLogger('tracker') + +PROFILE_IDX = 0 + +settings.init() +current_settings = settings.get() + +locator_init_params = {} + +if current_settings['app']['loc_method'] & settings.default_values_app._loc_method.gps: + locator_init_params['gps_cfg'] = { + 'UARTn': UART.UART0, + 'buadrate': 115200, + 'databits': 8, + 'parity': 0, + 'stopbits': 1, + 'flowctl': 0 + } + +if current_settings['app']['loc_method'] & settings.default_values_app._loc_method.cell: + locator_init_params['cellLocator_cfg'] = { + 'serverAddr': 'www.queclocator.com', + 'port': 80, + 'token': 'xGP77d2z0i91s67n', + 'timeout': 3, + 'profileIdx': PROFILE_IDX + } + +if current_settings['app']['loc_method'] & settings.default_values_app._loc_method.wifi: + locator_init_params['wifiLocator_cfg'] = { + 'token': 'xGP77d2z0i91s67n' + } + +def loc_read_cb(data): + if data: + loc_method = data[0] + loc_data = data[1] + log.info("loc_method:", loc_method) + log.info("loc_data:", loc_data) + if loc_method == settings.default_values_app._loc_method.gps: + data_type = tracker.remote.DATA_LOCA_GPS + else: + data_type = tracker.remote.DATA_LOCA_NON_GPS + tracker.remote.post_data(data_type, loc_data) + +tracker = Tracker(loc_read_cb, **locator_init_params) + +def loc_timer_cb(argv): + tracker.trigger() + +if (current_settings['app']['loc_mode'] & settings.default_values_app._loc_mode.cycle) and current_settings['app']['loc_cycle_period']: + loc_timer = osTimer() + loc_timer.start(current_settings['app']['loc_cycle_period'] * 1000, 1, loc_timer_cb) diff --git a/code/ota.py b/code/ota.py new file mode 100644 index 0000000..e69de29 diff --git a/code/quecthing.py b/code/quecthing.py new file mode 100644 index 0000000..b20cdb1 --- /dev/null +++ b/code/quecthing.py @@ -0,0 +1,80 @@ + +import quecIot +from usr.logging import getLogger + +DATA_NON_LOCA = 0x0 +DATA_LOCA_NON_GPS = 0x1 +DATA_LOCA_GPS = 0x2 + +log = getLogger('QuecThing') + +class QuecThing(object): + def __init__(self, pk, ps, downlink_queue): + self.downlink_queue = downlink_queue + quecIot.init() + quecIot.setEventCB(self.eventCB) + quecIot.setProductinfo(pk, ps) + quecIot.setServer(1, "iot-south.quectel.com:2883") + quecIot.setConnmode(1) + + def post_data(self, data_type, data): + if data_type == DATA_NON_LOCA: + quecIot.passTransSend(1, data) + elif data_type == DATA_LOCA_GPS: + quecIot.locReportOutside(data) + elif data_type == DATA_LOCA_NON_GPS: + quecIot.locReportInside(data) + else: + raise ValueError('No such locator (0x%X).' % data_type) + + def eventCB(self, data): + log.info("event:", data) + event = data[0] + errcode = data[1] + if len(data) > 2: + data = data[2] + + if event == 1: + if errcode == 10200: + log.info('Device authentication succeeded.') + elif errcode == 10422: + log.info('Device has been authenticated (connect failed).') + elif event == 2: + if errcode == 10200: + log.info('Access succeeded.') + elif event == 3: + if errcode == 10200: + log.info('Subscription succeeded.') + elif event == 4: + if errcode == 10200: + log.info('Data sending succeeded.') + elif errcode == 10210: + log.info('Object model data sending succeeded.') + elif errcode == 10220: + log.info('Location data sending succeeded.') + elif errcode == 10300: + log.info('Data sending failed.') + elif errcode == 10310: + log.info('Object model data sending failed.') + elif errcode == 10320: + log.info('Location data sending failed.') + elif event == 5: + if errcode == 10200: + log.info('Recving raw data.') + log.info(data) + ''' + self.downlink_queue.put(data) + ''' + if errcode == 10210: + log.info('Recving object model data.') + ''' + self.downlink_queue.put(data) + ''' + elif errcode == 10211: + log.info('Recving object model query command.') + elif event == 6: + if errcode == 10200: + log.info('Logout succeeded.') + elif event == 7: + if errcode == 10700: + log.info('New OTA plain.') diff --git a/code/remote.py b/code/remote.py new file mode 100644 index 0000000..b115eba --- /dev/null +++ b/code/remote.py @@ -0,0 +1,210 @@ + +from logging import exception +import utime +import ql_fs +import ujson +import uos +import _thread +from queue import Queue +import usr.settings as settings +import usr.dev_info as dev_info + +current_settings = settings.get() + +if current_settings['sys']['cloud'] == settings.default_values_sys._cloud.quecIot: + from usr.quecthing import QuecThing + +class RemoteError(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +def downlink_process(argv): + self = argv + while True: + ''' + Recv data from quecIot or AliYun or other server. + Data format should be unified at the process module file of its own before put to downlink_queue. + + Data format: + TODO: ===================== + ''' + msg = self.downlink_queue.get() + if msg: + pass + ''' + TODO: processing for settings or control commands from downlink channel + ''' + +def uplink_process(argv): + self = argv + ret = False + while True: + + ''' + We need to post data in tracker_data.hist file to server firstly every time. + If still can't post all data to server, stop posting, but to append all data in uplink_queue to tracker_data.hist. + When data in tracker_data.hist and in uplink_queue is processed, wait for new data coming into uplink_queue. + If get new data, try to post data again, if fail, add data to tracker_data.hist file. + Otherwise, keep waiting untill new data coming, then process could go to the start of loopwhile, and data in tracker_data.hist could be processed again. + ''' + + need_refresh = False + + # Read history data that didn't send to server intime to hist-dictionary. + hist = self.read_history() + try: + for key, value in hist.items(): + # Check if non_loca data (sensor or device info data) or location gps data or location non-gps data (cell/wifi-locator data) + if key == 'non_loca' or key == 'loca_non_gps' or key == 'loca_gps': + if key == 'non_loca': + data_type = self.DATA_NON_LOCA + elif key == 'loca_non_gps': + data_type = self.DATA_LOCA_NON_GPS + else: + data_type = self.DATA_LOCA_GPS + for i, data in enumerate(value): + ntry = 0 + # Try at most 3 times to post data to server. + while not self.cloud.post_data(data_type, data): + ntry += 1 + if ntry >= 3: # Data post failed after 3 times, maybe network error? + raise RemoteError('Data post failed.') # Stop posting more data, go to exception handler. + utime.sleep(1) + else: + value.pop(i) # Pop data from data-list after posting sueecss. + need_refresh = True # Data in hist-dictionary changed, need to refresh history file. + except Exception: + while True: # Put all data in uplink_queue to hist-dictionary. + if self.uplink_queue.size(): + msg = self.uplink_queue.get() + if msg: + if msg[0] == self.DATA_NON_LOCA: + key = 'non_loca' + elif msg[0] == self.DATA_LOCA_NON_GPS: + key = 'loca_non_gps' + elif msg[0] == self.DATA_LOCA_GPS: + key = 'loca_gps' + else: + continue + hist[key].append(msg[1]) + need_refresh = True + else: + continue + else: + break + finally: + if need_refresh: + # Flush data in hist-dictionary to tracker_data.hist file. + self.refresh_history(hist) + need_refresh = False + + ''' + If history data exists, put a empty msg to uplink_queue to trriger the return of self.uplink_queue.get() API below. + So that history data could be processed again immediately. + Without this, history data could only be processed after new data being put into uplink_queue. + But is this necessary ??? + ''' + if len(hist['non_loca']) + len(hist['loca_non_gps']) + len(hist['loca_gps']): + self.uplink_queue.put(()) + + # When comes to this, wait for new data coming into uplink_queue. + msg = self.uplink_queue.get() + if msg: + if msg[0] == self.DATA_NON_LOCA or msg[0] == self.DATA_LOCA_NON_GPS or msg[0] == self.DATA_LOCA_GPS: + if not self.cloud.post_data(msg[0], msg[1]): + self.add_history(msg[0], msg[1]) + else: + continue + else: + continue + else: + continue + +class Remote(object): + _history = '/usr/tracker_data.hist' + + def __init__(self): + self.downlink_queue = Queue(maxsize=64) + self.uplink_queue = Queue(maxsize=64) + if current_settings['sys']['cloud'] == settings.default_values_sys._cloud.quecIot: + self.cloud = QuecThing(dev_info.quecIot['PK'], dev_info.quecIot['PS'], self.downlink_queue) + self.DATA_NON_LOCA = QuecThing.DATA_NON_LOCA + self.DATA_LOCA_NON_GPS = QuecThing.DATA_LOCA_NON_GPS + self.DATA_LOCA_GPS = QuecThing.DATA_LOCA_GPS + else: + raise settings.Error('Current cloud (0x%X) not supported!' % current_settings['sys']['cloud']) + + _thread.start_new_thread(downlink_process, (self,)) + _thread.start_new_thread(uplink_process, (self,)) + + def read_history(self): + ''' + { + "non_loca": [ + {xxx}, + {xxx} + ], + + "loca_non_gps": [ + {xxx}, + {xxx} + ], + + "loca_gps": [ + {xxx}, + {xxx} + ] + } + ''' + if ql_fs.path_exists(self._history): + with open(self._history, 'r') as f: + try: + res = ujson.load(f) + if isinstance(res, dict): + return res + return {} + except Exception: + return {} + else: + return {} + + def add_history(self, data_type, data): + try: + with open(self._history, 'r') as f: + res = ujson.load(f) + except Exception: + res = {} + + if not isinstance(res, dict): + res = {} + + if data_type == self.DATA_NON_LOCA: + key = 'non_loca' + elif data_type == self.DATA_LOCA_NON_GPS: + key = 'loca_non_gps' + elif data_type == self.DATA_LOCA_GPS: + key = 'loca_gps' + + if key not in res: + res[key] = [] + + res[key].append(data) + + return self.refresh_history(res) + + def refresh_history(self, hist_dict): + try: + with open(self._history, 'w') as f: + ujson.dump(hist_dict, f, indent = 4) + return True + except Exception: + return False + + def clean_history(self): + uos.remove(self._history) + + def post_data(self, data_type, data): + self.uplink_queue.put((data_type, data)) diff --git a/code/selfcheck.py b/code/selfcheck.py new file mode 100644 index 0000000..33d4f20 --- /dev/null +++ b/code/selfcheck.py @@ -0,0 +1,17 @@ + +''' +check if network, gps, and all the sensors work normally +''' + + +def netcheck(): + # return True if OK + pass + +def gps_check(): + # return True if OK + pass + +def sensor_check(): + # return True if OK + pass \ No newline at end of file diff --git a/code/sensor.py b/code/sensor.py new file mode 100644 index 0000000..478869b --- /dev/null +++ b/code/sensor.py @@ -0,0 +1,4 @@ + +class Sensor(): + def __init__(self): + pass \ No newline at end of file diff --git a/code/settings.py b/code/settings.py new file mode 100644 index 0000000..05e02fb --- /dev/null +++ b/code/settings.py @@ -0,0 +1,171 @@ + +import ql_fs +import ujson +import uos +import ure + +tracker_settings_file = '/usr/tracker_settings.json' + +class default_values_app(object): + ''' + App default settings + ''' + + class _loc_method(object): + none = 0x0 + gps = 0x1 + cell = 0x2 + wifi = 0x4 + all = 0x7 + + class _loc_mode(object): + none = 0x0 + cycle = 0x1 + onAlert = 0x2 + onPhoneCall = 0x4 + onVoiceRecord = 0x8 + all = 0xF + + ''' + variables of App default settings below MUST NOT start with '_' + ''' + + phone_num = '' + + loc_method = _loc_method.gps + + loc_mode = _loc_mode.cycle + + loc_cycle_period = 1 + + sw_ota = True + + sw_auto_upgrade = True + + sw_electric_fence = True + + sw_phone_call = False + + sw_voice_record = False + + sw_jtt808 = True + + sw_fault_alert = True + + sw_low_power_alert = True + + sw_over_speed_alert = True + + sw_sim_out_alert = True + + sw_disassemble_alert = True + + sw_vibrate_alert = True + + sw_drive_behavior_alert = True + + +class default_values_sys(object): + ''' + System default settings + ''' + + class _cloud(object): + none = 0x0 + quecIot = 0x1 + AliYun = 0x2 + JTT808 = 0x4 + customization = 0x8 + + ''' + variables of system default settings below MUST NOT start with '_' + ''' + + cloud = _cloud.quecIot + + +default_settings_app = {k:v for k,v in default_values_app.__dict__.items() if not k.startswith('_')} +current_settings_app = {} + +default_settings_sys = {k:v for k,v in default_values_sys.__dict__.items() if not k.startswith('_')} +current_settings_sys = {} + +default_settings = {'app':default_settings_app, 'sys':default_settings_sys} +current_settings = {} + +def init(): + global current_settings + if not ql_fs.path_exists(tracker_settings_file): + with open(tracker_settings_file, 'w') as f: + ujson.dump(default_settings, f, indent = 4) + current_settings = dict(default_settings) + else: + with open(tracker_settings_file, 'r') as f: + current_settings = ujson.load(f) + +def get(): + global current_settings + return current_settings + +def set(opt, val): + if opt in current_settings['app']: + if opt == 'phone_num': + if not isinstance(val, str): + return False + pattern = ure.compile(r'^(?:(?:\+)86)?1[3-9]\d{9}$') + if pattern.search(val): + current_settings['app'][opt] = val + return True + return False + + elif opt == 'loc_method': + if not isinstance(val, int): + return False + if val > default_values_app._loc_method.all: + return False + current_settings['app'][opt] = val + return True + + elif opt == 'loc_mode': + if not isinstance(val, int): + return False + if val > default_values_app._loc_mode.all: + return False + current_settings['app'][opt] = val + return True + + elif opt == 'loc_cycle_period': + if not isinstance(val, int): + return False + if val < 1: + return False + current_settings['app'][opt] = val + return True + + elif opt == 'sw_ota' or opt == 'sw_auto_upgrade' or opt == 'sw_electric_fence' or opt == 'sw_phone_call' or opt == 'sw_voice_record' \ + or opt == 'sw_jtt808' or opt == 'sw_fault_alert' or opt == 'sw_low_power_alert' or opt == 'sw_over_speed_alert' or opt == 'sw_sim_out_alert' \ + or opt == 'sw_disassemble_alert' or opt == 'sw_vibrate_alert' or opt == 'sw_drive_behavior_alert': + if not isinstance(val, bool): + return False + current_settings['app'][opt] = val + return True + + else: + return False + + else: + return False + +def save(): + with open(tracker_settings_file, 'w') as f: + ujson.dump(current_settings, f, indent = 4) + +def reset(): + uos.remove(tracker_settings_file) + +class Error(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) diff --git a/code/tracker.py b/code/tracker.py new file mode 100644 index 0000000..43779c7 --- /dev/null +++ b/code/tracker.py @@ -0,0 +1,12 @@ + +from usr.location import Location +from usr.location import Remote +from usr.sensor import Sensor +from usr.led import LED + +class Tracker(): + def __init__(self, loc_read_cb, **kw): + self.locator = Location(loc_read_cb, **kw) + self.remote = Remote() + self.sensor = Sensor() + self.led = LED() diff --git a/docs/tracker功能定义.xlsx b/docs/tracker功能定义.xlsx new file mode 100644 index 0000000..2eedae9 Binary files /dev/null and b/docs/tracker功能定义.xlsx differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b4fbe11 --- /dev/null +++ b/readme.md @@ -0,0 +1,6 @@ + +# Tracker-v2.0.0 + +## Script files are in code directory + +## Function definitions file is in docs directory