From fbd42c42f9a8109b4a1949b22a9b8a46fa906baa Mon Sep 17 00:00:00 2001 From: JackSun-qc Date: Thu, 31 Mar 2022 13:41:08 +0800 Subject: [PATCH] update: Quecthing OTA --- code/location.py | 8 +- code/logging.py | 3 +- code/main.py | 6 +- code/quecthing.py | 221 +++++++++++++++++++++++++++++++++++++++++-- code/remote.py | 11 +-- code/test_tracker.py | 29 ++++-- code/tracker.py | 2 +- 7 files changed, 249 insertions(+), 31 deletions(-) diff --git a/code/location.py b/code/location.py index ff42956..068e019 100644 --- a/code/location.py +++ b/code/location.py @@ -108,7 +108,7 @@ class GPS(Singleton): global gps_data_retrieve_queue while self.__first_break == 0: - self.__gps_timer.start(50, 1, self.first_gps_timer_callback) + self.__gps_timer.start(50, 0, self.first_gps_timer_callback) nread = gps_data_retrieve_queue.get() data = self.uart_obj.read(nread).decode() self.__gps_timer.stop() @@ -119,7 +119,7 @@ class GPS(Singleton): gga_data = '' vtg_data = '' while self.__second_break == 0: - self.__gps_timer.start(1500, 1, self.second_gps_timer_callback) + self.__gps_timer.start(1500, 0, self.second_gps_timer_callback) nread = gps_data_retrieve_queue.get() if nread: data += self.uart_obj.read(nread).decode() @@ -141,7 +141,7 @@ class GPS(Singleton): quecgnss.gnssEnable(1) while self.__first_break == 0: - self.__gps_timer.start(50, 1, self.first_gps_timer_callback) + self.__gps_timer.start(50, 0, self.first_gps_timer_callback) data = quecgnss.read(1024) self.__gps_timer.stop() self.__first_break = 0 @@ -152,7 +152,7 @@ class GPS(Singleton): vtg_data = '' count = 0 while self.__second_break == 0: - self.__gps_timer.start(1500, 1, self.second_gps_timer_callback) + self.__gps_timer.start(1500, 0, self.second_gps_timer_callback) gnss_data = quecgnss.read(1024) if gnss_data and gnss_data[1]: data += gnss_data[1].decode() if len(gnss_data) > 1 and gnss_data[1] else '' diff --git a/code/logging.py b/code/logging.py index e8fe6d7..2d5c646 100644 --- a/code/logging.py +++ b/code/logging.py @@ -15,6 +15,8 @@ import utime from usr.settings import settings +current_settings = settings.get() + def asyncLog(name, level, *message, timeout=None, await_connection=True): ''' @@ -27,7 +29,6 @@ def asyncLog(name, level, *message, timeout=None, await_connection=True): def log(name, level, *message, local_only=False, return_only=False, timeout=None): - current_settings = settings.get() if not current_settings.get('sys', {}).get('sw_log', True): return diff --git a/code/main.py b/code/main.py index fc70172..8dd6bb7 100644 --- a/code/main.py +++ b/code/main.py @@ -15,14 +15,12 @@ from usr.tracker import Tracker from usr.settings import settings from usr.settings import default_values_sys +from usr.settings import PROJECT_NAME +from usr.settings import PROJECT_VERSION from usr.logging import getLogger log = getLogger(__name__) -PROJECT_NAME = 'QuecPython_Tracker' - -PROJECT_VERSION = '2.0.1' - def main(): log.info('PROJECT_NAME: %s' % PROJECT_NAME) diff --git a/code/quecthing.py b/code/quecthing.py index 33c352e..751a984 100644 --- a/code/quecthing.py +++ b/code/quecthing.py @@ -12,11 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import uos import utime import osTimer import quecIot +import uhashlib +import ubinascii +import uzlib +import ql_fs +import app_fota_download -# from misc import Power from queue import Queue from usr.logging import getLogger @@ -85,19 +90,44 @@ object_model_code = {i[1][0]: i[0] for i in object_model} class QuecThing(object): def __init__(self, pk, ps, dk, ds, downlink_queue): + self.pk = pk + self.ps = ps + self.dk = dk + self.ds = ds + self.init_res = {} + self.fileSize = 0 + self.needDownloadSize = 0 + self.crcValue = 0 + self.downloadSize = 0 + self.fileFp = 0 + self.startAddr = 0 self.downlink_queue = downlink_queue self.post_result_wait_queue = Queue(maxsize=16) self.quec_timer = osTimer() - self.queciot_init(pk, ps, dk, ds) + self.queciot_init() + + fileClear = OTAFileClear() + fileClear.file_clear() + + def queciot_init(self): + if quecIot.getWorkState() == 8 and quecIot.getConnmode() == 1: + return - def queciot_init(self, pk, ps, dk, ds): quecIot.init() quecIot.setEventCB(self.eventCB) - quecIot.setProductinfo(pk, ps) - quecIot.setDkDs(dk, ds) + quecIot.setProductinfo(self.pk, self.ps) + quecIot.setDkDs(self.dk, self.ds) quecIot.setServer(1, "iot-south.quectel.com:2883") quecIot.setConnmode(1) - if not ds and dk: + + count = 0 + while quecIot.getWorkState() != 8 and count < 10: + if self.init_res.get('subscription') is not None: + self.init_res.pop('subscription') + break + utime.sleep_ms(200) + + if not self.ds and self.dk: count = 0 while count < 3: ndk, nds = quecIot.getDkDs() @@ -108,6 +138,8 @@ class QuecThing(object): current_settings = settings.get() cloud_init_params = current_settings['sys']['cloud_init_params'] cloud_init_params['DS'] = nds + self.dk = ndk + self.ds = nds settings.set('cloud_init_params', cloud_init_params) settings.save() @@ -129,6 +161,7 @@ class QuecThing(object): del data[k] def post_data(self, data): + self.queciot_init() res = True # log.debug('post_data: %s' % str(data)) for k, v in data.items(): @@ -192,11 +225,25 @@ class QuecThing(object): elif event == 2: if errcode == 10200: log.info('Access succeeded.') + self.init_res['access'] = True if errcode == 10450: log.error('Device internal error (connect failed).') + self.init_res['access'] = False elif event == 3: if errcode == 10200: log.info('Subscription succeeded.') + self.init_res['subscription'] = True + quecIot.otaRequest(0) + if data != (3, 10200): + ota_info = data.decode() + file_info = ota_info.split(',') + log.info( + "OTA File Info: componentNo: %s, sourceVersion: %s, targetVersion: %s, " + "batteryLimit: %s, minSignalIntensity: %s, minSignalIntensity: %s" % tuple(file_info) + ) + if errcode == 10300: + log.info('Subscription failed.') + self.init_res['subscription'] = False elif event == 4: if errcode == 10200: log.info('Data sending succeeded.') @@ -239,16 +286,31 @@ class QuecThing(object): elif event == 7: if errcode == 10700: log.info('New OTA plain.') + ota_info = data.decode() + file_info = ota_info.split(',') + log.info( + "OTA File Info: componentNo: %s, sourceVersion: %s, targetVersion: %s, " + "batteryLimit: %s, minSignalIntensity: %s, useSpace: %s" % tuple(file_info) + ) self.downlink_queue.put(('ota_plain', data)) self.downlink_queue.put(('object_model', [('ota_status', 1)])) elif errcode == 10701: log.info('The module starts to download.') self.downlink_queue.put(('object_model', [('ota_status', 2)])) + if data != (7, 10701): + ota_info = data.decode() + file_info = ota_info.split(',') + self.sota_download_info(int(file_info[1]), file_info[2], int(file_info[3])) elif errcode == 10702: log.info('Package download.') self.downlink_queue.put(('object_model', [('ota_status', 2)])) elif errcode == 10703: log.info('Package download complete.') + if data != (7, 10703): + ota_info = data.decode() + file_info = ota_info.split(',') + log.info("OTA File Info: componentNo: %s, length: %s, md5: %s, crc: %s" % tuple(file_info)) + self.sota_download_success(file_info[2], file_info[3]) self.downlink_queue.put(('object_model', [('ota_status', 2)])) elif errcode == 10704: log.info('Package updating.') @@ -259,9 +321,156 @@ class QuecThing(object): elif errcode == 10706: log.info('Failed to update firmware.') self.downlink_queue.put(('object_model', [('ota_status', 4)])) + elif errcode == 10707: + log.info('Received confirmation broadcast.') def dev_info_report(self): quecIot.devInfoReport([i for i in range(1, 13)]) def ota_action(self, val=1): quecIot.otaAction(val) + + def sota_download_info(self, size, md5_value, crc): + self.file_size = size + self.crc_value = crc + self.download_size = 0 + self.update_mode = UpdateCtx() + self.md5_value = md5_value + + def read_sota_file(self): + while self.need_download_size != 0: + readsize = 4096 + if (readsize > self.need_download_size): + readsize = self.need_download_size + updateFile = quecIot.mcuFWDataRead(self.start_addr, readsize) + self.update_mode.write_update_data(updateFile) + log.debug("Download File Size: %s" % readsize) + self.need_download_size -= readsize + self.start_addr += readsize + self.download_size += readsize + if (self.download_size == self.file_size): + log.debug("File Download Success, Update Start.") + quecIot.otaAction(3) + file_update_res = self.update_mode.file_update(self.md5_value) + if file_update_res: + log.debug("File Update Success, Power Restart.") + self.downlink_queue.put(('object_model', [('ota_status', 3)])) + self.downlink_queue.put(('object_model', [('power_restart', 1)])) + else: + log.debug("File Update Failed.") + self.downlink_queue.put(('object_model', [('ota_status', 4)])) + else: + quecIot.otaAction(2) + + def sota_download_success(self, start, down_loaded_size): + self.need_download_size = down_loaded_size + self.start_addr = start + self.read_sota_file() + + +class UpdateCtx(object): + def __init__(self, parent_dir="/usr/.updater/usr/"): + self.fp = open("/usr/sotaFile.tar.gz", "wb+") + self.file_list = [] + self.parent_dir = parent_dir + self.unzipFp = 0 + self.hash_obj = uhashlib.md5() + + def write_update_data(self, data): + self.fp.write(data) + self.hash_obj.update(data) + + def __get_file_size(self, data): + size = data.decode('ascii') + size = size.rstrip('\0') + if (len(size) == 0): + return 0 + size = int(size, 8) + return size + + def __get_file_name(self, name): + fileName = name.decode('ascii') + fileName = fileName.rstrip('\0') + return fileName + + def file_update(self, md5_value): + md5Data = ubinascii.hexlify(self.hash_obj.digest()) + md5Data = md5Data.decode('ascii') + md5Value = eval(md5_value) + log.debug("DMP Calc MD5 Value: %s, Device Calc MD5 Value: %s" % (md5Value, md5Data)) + if (md5Value != md5Data): + log.error("MD5 Verification Failed") + return + + log.debug("MD5 Verification Success.") + self.fp.seek(10) + self.unzipFp = uzlib.DecompIO(self.fp, -15) + log.debug('Unzip File Success.') + ql_fs.mkdirs(self.parent_dir) + try: + while True: + data = self.unzipFp.read(0x200) + if not data: + log.debug("Read File Size Zore.") + break + size = self.__get_file_size(data[124:135]) + fileName = self.__get_file_name(data[:100]) + log.debug("File Name: %s, File Size: %s" % (fileName, size)) + if not size: + if len(fileName): + log.debug("Create File Dir: %s" % self.parent_dir + fileName) + ql_fs.mkdirs(self.parent_dir + fileName) + else: + log.debug("Have No File Unzip.") + break + else: + log.debug("File %s Write Size %s" % (self.parent_dir + fileName, size)) + fp = open(self.parent_dir + fileName, "wb+") + fileSize = size + while fileSize: + data = self.unzipFp.read(0x200) + if (fileSize < 0x200): + fp.write(data[:fileSize]) + fileSize = 0 + fp.close() + self.file_list.append({"fileName": "/usr/" + fileName, "size": size}) + break + else: + fileSize -= 0x200 + fp.write(data) + + for fileName in self.file_list: + app_fota_download.update_download_stat("/usr/.updater" + fileName["fileName"], fileName["fileName"], fileName["size"]) + app_fota_download.set_update_flag() + self.fp.close() + log.debug("Remove /usr/sotaFile.tar.gz") + uos.remove("/usr/sotaFile.tar.gz") + except Exception as e: + log.error("Unpack Error: %s" % e) + return False + return True + + +class OTAFileClear(object): + def __init__(self): + self.usrList = uos.ilistdir("/usr/") + + def __remove_updater_dir(self, path): + dirList = uos.ilistdir(path) + for fileInfo in dirList: + if fileInfo[1] == 0x4000: + self.__remove_updater_dir("%s/%s" % (path, fileInfo[0])) + else: + log.debug("remove file name: %s/%s" % (path, fileInfo[0])) + uos.remove("%s/%s" % (path, fileInfo[0])) + + log.debug("remove dir name: %s" % path) + uos.remove(path) + + def file_clear(self): + for fileInfo in self.usrList: + if fileInfo[0] == ".updater": + self.__remove_updater_dir("/usr/.updater") + elif fileInfo[0] == "sotaFile.tar.gz": + log.debug("remove update file sotaFile.tar.gz") + uos.remove("/usr/sotaFile.tar.gz") diff --git a/code/remote.py b/code/remote.py index 7aac9c2..9b975b6 100644 --- a/code/remote.py +++ b/code/remote.py @@ -163,8 +163,6 @@ def downlink_process(argv): option_fun(*args) if self.remote_read_cb: self.remote_read_cb(*data) - else: - log.warn('Remote read callback is not defined.') else: # TODO: Raise Error OR Conntinue raise RemoteError('DownLinkOption has no accribute %s.' % option_attr) @@ -204,7 +202,7 @@ def uplink_process(argv): 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 as e: - log.error('uplink_process error: %s' % e) + log.error('uplink_process Error: %s' % e) while True: # Put all data in uplink_queue to hist-dictionary. if self.uplink_queue.size(): data = self.uplink_queue.get() @@ -335,11 +333,12 @@ class Remote(Singleton): current_settings = settings.settings.get() if current_settings['sys']['cloud'] == settings.default_values_sys._cloud.quecIot: if current_settings['app']['sw_ota'] is True: + log.debug('OTA Check To Report Dev Info.') self.cloud.dev_info_report() else: - raise settings.SettingsError('OTA upgrade is disabled!') + raise settings.SettingsError('OTA Upgrade Is Disabled!') else: - raise settings.SettingsError('Current cloud (0x%X) not supported!' % current_settings['sys']['cloud']) + raise settings.SettingsError('Current Cloud (0x%X) Not Supported!' % current_settings['sys']['cloud']) def cloud_ota_action(self, val=1): current_settings = settings.settings.get() @@ -349,4 +348,4 @@ class Remote(Singleton): settings.settings.set('ota_status', 0) settings.settings.save() else: - raise settings.SettingsError('Current cloud (0x%X) not supported!' % current_settings['sys']['cloud']) + raise settings.SettingsError('Current Cloud (0x%X) Not Supported!' % current_settings['sys']['cloud']) diff --git a/code/test_tracker.py b/code/test_tracker.py index f934228..ca8768a 100644 --- a/code/test_tracker.py +++ b/code/test_tracker.py @@ -16,6 +16,7 @@ import pm import ure import utime import _thread +import osTimer from queue import Queue from machine import RTC @@ -118,21 +119,21 @@ def test_tracker(): device_check_res = tracker.device_check() log.info('[.] device_check_res:', device_check_res) - log.info('[.] sleep 3') - utime.sleep(3) + log.info('[.] sleep 10') + utime.sleep(10) - log.info('[.] tracker.power_manage.low_energy_init()') - tracker.power_manage.low_energy_init() - log.info('[.] tracker.power_manage.start_rtc()') - tracker.power_manage.start_rtc() - log.info('[.] end tracker.power_manage.start_rtc()') + # log.info('[.] tracker.power_manage.low_energy_init()') + # tracker.power_manage.low_energy_init() + # log.info('[.] tracker.power_manage.start_rtc()') + # tracker.power_manage.start_rtc() + # log.info('[.] end tracker.power_manage.start_rtc()') # log.info('[.] test tracker.device_check()') # device_check_res = tracker.device_check() # log.info('[.] device_check_res:', device_check_res) - # log.info('[.] test tracker.remote.check_ota()') - # tracker.remote.check_ota() + log.info('[.] test tracker.remote.check_ota()') + tracker.remote.check_ota() # log.info('[.] sleep 3') # utime.sleep(3) @@ -264,6 +265,15 @@ def test_gps_uart(): log.debug('[test_gps_uart] gps_info size: %s' % len(gps_info)) +def timer_cb(args): + print('[%s] timer callback' % utime.mktime(utime.localtime())) + + +def test_ostimer(): + timer = osTimer() + timer.start(1000, 2, timer_cb) + + def main(): # test_quecthing() # test_settings() @@ -276,6 +286,7 @@ def main(): # test_pm() # test_rtc() # test_gps_uart() + # test_ostimer() if __name__ == '__main__': diff --git a/code/tracker.py b/code/tracker.py index 5f392d6..b5671ed 100644 --- a/code/tracker.py +++ b/code/tracker.py @@ -182,7 +182,7 @@ class Tracker(Singleton): return str(num) def data_report_cb(self, topic, msg): - log.debug('[x] recive res topic [%s]' % topic) + log.debug('[x] recive res topic [%s] msg [%s]' % (topic, msg)) sys_bus.unsubscribe(topic) if topic.endswith('/wakelock_unlock'):