diff --git a/code/ota.py b/code/ota.py index 5ab34a9..7172e07 100644 --- a/code/ota.py +++ b/code/ota.py @@ -12,8 +12,198 @@ # See the License for the specific language governing permissions and # limitations under the License. +import uos +import fota +import app_fota +import uzlib +import ql_fs +import uhashlib +import ubinascii +import app_fota_download +from queue import Queue + +from usr.logging import getLogger +from usr.settings import SYSNAME +from usr.settings import PROJECT_NAME + +log = getLogger(__name__) + def OTA(object): - def __init__(self) - pass + def __init__(self, module, file_info): + self.module = module + self.file_info = file_info + self.fota_queue = Queue(maxsize=4) + + def start(self): + if self.module == SYSNAME: + return self.start_fota() + elif self.module == PROJECT_NAME: + return self.start_sota() + else: + log.error('OTA Module %s Is Not Error!' % self.module) + return False + + def start_fota(self): + fota_obj = fota() + url1 = self.file_info[0]['url'] + url2 = self.file_info[1]['url'] if len(self.file_info) > 1 else '' + res = fota_obj.httpDownload(url1=url1, url2=url2, callback=self.fota_cb) + if res == 0: + fota_res = self.fota_queue.get() + return fota_res + else: + return False + + def fota_cb(self, args): + down_status = args[0] + down_process = args[1] + if down_status != -1: + log.debug('DownStatus: %s [%s][%s%%]' % (down_status, '=' * down_process, down_process)) + if down_status == 0 and down_process == 100: + self.fota_queue.put(True) + # TODO: Report To Cloud Upgrade Process. + else: + log.error('Down Failed. Error Code: %s' % down_process) + self.fota_queue.put(False) + + def start_sota(self): + ota_module_obj = SotaDownloadUpgrade() + for file in self.file_info: + if ota_module_obj.app_fota_down(file['url']): + if ota_module_obj.file_update(file['md5']): + continue + else: + return False + else: + return False + ota_module_obj.sota_set_flag() + + return True + + +class SotaDownloadUpgrade(object): + def __init__(self, parent_dir="/usr/.updater/usr/"): + self.fp_file = "/usr/sotaFile.tar.gz" + self.file_list = [] + self.parent_dir = parent_dir + self.unzipFp = 0 + self.hash_obj = uhashlib.md5() + + def write_update_data(self, data): + with open(self.fp_file, "wb+") as fp: + fp.write(data) + self.hash_obj.update(data) + + def app_fota_down(self, url): + app_fota_obj = app_fota.new() + fp = open(self.fp_file, "wb+") + fp.close() + res = app_fota_obj.download(url, self.fp_file) + if res == 0: + self.hash_obj = uhashlib.md5() + with open(self.fp_file, "rb+") as fp: + for fpi in fp.readlines(): + self.hash_obj.update(fpi) + return True + else: + return False + + 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.") + ota_file = open(self.fp_file, "wb+") + ota_file.seek(10) + self.unzipFp = uzlib.DecompIO(ota_file, -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"]) + ota_file.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 + + def sota_set_flag(self): + app_fota_download.set_update_flag() + + +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/quecthing.py b/code/quecthing.py index ea54708..a5b7797 100644 --- a/code/quecthing.py +++ b/code/quecthing.py @@ -12,15 +12,10 @@ # 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 queue import Queue @@ -28,6 +23,8 @@ from usr.logging import getLogger from usr.settings import settings from usr.settings import PROJECT_NAME from usr.settings import PROJECT_VERSION +from usr.ota import SotaDownloadUpgrade +from usr.ota import OTAFileClear log = getLogger(__name__) @@ -309,8 +306,8 @@ class QuecThing(object): "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', (data[0], 1, data[2]))])) + self.downlink_queue.put(('ota_plain', data)) elif errcode == 10701: log.info('The module starts to download.') if data != (7, 10701): @@ -351,7 +348,7 @@ class QuecThing(object): self.file_size = size self.crc_value = crc self.download_size = 0 - self.update_mode = UpdateCtx() + self.update_mode = SotaDownloadUpgrade() self.md5_value = md5_value def sota_download_success(self, start, down_loaded_size): @@ -375,117 +372,10 @@ class QuecThing(object): quecIot.otaAction(3) file_update_res = self.update_mode.file_update(self.md5_value) if file_update_res: + self.update_mode.sota_set_flag() log.debug("File Update Success, Power Restart.") else: log.debug("File Update Failed.") self.downlink_queue.put(('object_model', [('power_restart', 1)])) else: quecIot.otaAction(2) - - -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 5852179..e00c0b3 100644 --- a/code/remote.py +++ b/code/remote.py @@ -22,11 +22,14 @@ from queue import Queue import usr.settings as settings +from usr.ota import OTA from usr.common import Singleton from usr.logging import getLogger from usr.settings import DATA_NON_LOCA -from usr.settings import DATA_LOCA_NON_GPS from usr.settings import DATA_LOCA_GPS +from usr.settings import DATA_LOCA_NON_GPS +from usr.settings import SYSNAME +from usr.settings import PROJECT_NAME if settings.settings.get()['sys']['cloud'] == settings.default_values_sys._cloud.quecIot: from usr.quecthing import QuecThing @@ -87,17 +90,18 @@ class Controller(Singleton): if upgrade_info: current_settings = settings.settings.get() ota_status_info = current_settings['sys']['ota_status'] - ota_info = {} - if upgrade_info[0] == settings.SYSNAME: - ota_info['upgrade_module'] = 1 - ota_info['sys_target_version'] = upgrade_info[2] - elif upgrade_info[0] == settings.PROJECT_NAME: - ota_info['upgrade_module'] = 2 - ota_info['app_target_version'] = upgrade_info[2] - ota_info['upgrade_status'] = upgrade_info[1] - ota_status_info.update(ota_info) - settings.settings.set('ota_status', ota_status_info) - settings.settings.save() + if ota_status_info['sys_target_version'] == '--' and ota_status_info['app_target_version'] == '--': + ota_info = {} + if upgrade_info[0] == settings.SYSNAME: + ota_info['upgrade_module'] = 1 + ota_info['sys_target_version'] = upgrade_info[2] + elif upgrade_info[0] == settings.PROJECT_NAME: + ota_info['upgrade_module'] = 2 + ota_info['app_target_version'] = upgrade_info[2] + ota_info['upgrade_status'] = upgrade_info[1] + ota_status_info.update(ota_info) + settings.settings.set('ota_status', ota_status_info) + settings.settings.save() def power_restart(self, perm, flag): if perm == 'w': @@ -155,10 +159,11 @@ class DownLinkOption(object): current_settings = settings.settings.get() if current_settings['app']['sw_ota'] and current_settings['app']['sw_ota_auto_upgrade']: if current_settings['sys']['cloud'] == settings.default_values_sys._cloud.quecIot: - self.tracker.remote.cloud_ota_action() + self.tracker.remote.cloud_ota_action(val=1) elif current_settings['sys']['cloud'] == settings.default_values_sys._cloud.AliYun: log.debug('ota_plain args: %s' % str(args)) log.debug('ota_plain kwargs: %s' % str(kwargs)) + self.tracker.remote.cloud_ota_action(val=1, kwargs=kwargs) def ota_file_download(self, *args, **kwargs): log.debug('ota_file_download: %s' % str(args)) @@ -387,20 +392,24 @@ class Remote(Singleton): else: log.error('Current Cloud (0x%X) Not Supported!' % current_settings['sys']['cloud']) - def cloud_ota_action(self, val=1): + def cloud_ota_action(self, val=1, kwargs=None): current_settings = settings.settings.get() if current_settings['sys']['cloud'] == settings.default_values_sys._cloud.quecIot: self.cloud.ota_action(val) + if val == 0: + self.tracker.ota_params_reset() + else: if val == 0: current_settings = settings.settings.get() ota_status_info = current_settings['sys']['ota_status'] - ota_info = {} - ota_info['sys_target_version'] = '--' - ota_info['app_target_version'] = '--' - ota_info['upgrade_module'] = 0 - ota_info['upgrade_status'] = 0 - ota_status_info.update(ota_info) - settings.settings.set('ota_status', ota_status_info) - settings.settings.save() - else: - log.warn('Current Cloud (0x%X) Not Supported!' % current_settings['sys']['cloud']) + upgrade_module = SYSNAME if ota_status_info['upgrade_module'] == 1 else PROJECT_NAME + self.cloud.ota_device_progress(step=-1, desc='User cancels upgrade.', module=upgrade_module) + self.tracker.ota_params_reset() + else: + upgrade_module = kwargs.get('module', '') + file_info = [{'size': i['fileSize'], 'url': i['fileUrl'], 'md5': i['fileMd5']} for i in kwargs.get('files', [])] + if not file_info: + file_info = [{'size': kwargs['size'], 'url': kwargs['url'], 'md5': kwargs['md5']}] + ota_obj = OTA(upgrade_module, file_info) + ota_obj.start() + self.downlink_queue.put(('object_model', [('power_restart', 1)])) diff --git a/code/tracker.py b/code/tracker.py index de46845..4e1c551 100644 --- a/code/tracker.py +++ b/code/tracker.py @@ -222,14 +222,7 @@ class Tracker(Singleton): current_settings = settings.settings.get() ota_status_info = current_settings['sys']['ota_status'] if ota_status_info['upgrade_status'] in (3, 4): - ota_info = {} - ota_info['sys_target_version'] = '--' - ota_info['app_target_version'] = '--' - ota_info['upgrade_module'] = 0 - ota_info['upgrade_status'] = 0 - ota_status_info.update(ota_info) - settings.settings.set('ota_status', ota_status_info) - settings.settings.save() + self.ota_params_reset() def device_check(self): device_check_res = self.get_device_check() @@ -278,6 +271,18 @@ class Tracker(Singleton): if net_check_res == (3, 1): pass + def ota_params_reset(self): + current_settings = settings.settings.get() + ota_status_info = current_settings['sys']['ota_status'] + ota_info = {} + ota_info['sys_target_version'] = '--' + ota_info['app_target_version'] = '--' + ota_info['upgrade_module'] = 0 + ota_info['upgrade_status'] = 0 + ota_status_info.update(ota_info) + settings.settings.set('ota_status', ota_status_info) + settings.settings.save() + class SelfCheck(object):