diff --git a/API文档/HeliosServices框架使用及API文档.pdf b/API文档/HeliosServices框架使用及API文档.pdf new file mode 100644 index 0000000..dddbe3f Binary files /dev/null and b/API文档/HeliosServices框架使用及API文档.pdf differ diff --git a/API文档/三方组件文档/offline_storage.pdf b/API文档/三方组件文档/offline_storage.pdf new file mode 100644 index 0000000..2e7e3a1 Binary files /dev/null and b/API文档/三方组件文档/offline_storage.pdf differ diff --git a/API文档/三方组件文档/中断及看门狗文档.pdf b/API文档/三方组件文档/中断及看门狗文档.pdf new file mode 100644 index 0000000..7f150a7 Binary files /dev/null and b/API文档/三方组件文档/中断及看门狗文档.pdf differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..39c2b17 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# HeliosService + + + +**API文档 - 里面有API文档** + +**helios service文档 \- 框架文档** + +**Code \- 代码** + + + + + +**API documentation - It has API documentation** + +**Helios Service document \- Framework document** + +**Code \- code** + diff --git a/code/usr/bin/cloud_service.py b/code/usr/bin/cloud_service.py new file mode 100644 index 0000000..38407cf --- /dev/null +++ b/code/usr/bin/cloud_service.py @@ -0,0 +1,98 @@ +__all__ = [ + "CloudService", + "CloudServiceMonitor", +] + +from usr.bin.components.abstract_service import AbstractService +from usr.bin.components.monitor import ServiceMonitor +from usr.utils.service_utils import Singleton +from usr.bin.components.OTA import UCloudOTA +import _thread + +SH_UPGRADE = 1 +BIN_UPGRADE = 0 +ENABLE_ = "enable" + + +class CloudServiceMonitor(ServiceMonitor): + @staticmethod + def create_monitor(config=None): + if config is None: + return None + level = 0 + try: + level = config.get("level", 0) + uid = config["params"]["uid"] + module_type = config["params"]["module_type"] + pk = config["params"]["pk"] + battery = config["params"].get("battery", 100) + reboot = config["params"].get("reboot", False) + cloud_service = CloudService(uid=uid, module_type=module_type, pk=pk, battery=battery, reboot=bool(reboot)) + except Exception as e: + return None + else: + m = CloudServiceMonitor(cloud_service) + m.set_level(level) + if config is not None: + m.set_exception_handlers(config.get('exceptionHandlers', None)) + return m + + +@Singleton +class CloudService(AbstractService): + def __init__(self, uid, module_type, pk, battery=100, reboot=False): + super().__init__("CLOUD") + self.reboot = reboot + self.module_type = module_type + self.pk = pk + self.uid = uid + self.ota_upgrade = UCloudOTA(battery) + self.__upgrade_status_info = { + SH_UPGRADE: None, + BIN_UPGRADE: None + } + self.server_status = { + SH_UPGRADE: {ENABLE_: True}, + BIN_UPGRADE: {ENABLE_: False} + } + + @property + def upgrade_status_info(self): + return self.__upgrade_status_info + + def set_enable(self, sr, enable): + """set start""" + self.server_status[sr][ENABLE_] = enable + + def __upgrade_sh(self): + # 触发升级脚本操作 + if self.server_status[SH_UPGRADE][ENABLE_]: + code = self.ota_upgrade.start_upgrade_sh_event(self.module_type, self.uid, self.pk, reboot=self.reboot) + self.send_msg(message=dict(code=code), msg_type=SH_UPGRADE) + + def __upgrade_bin(self): + # 触发升级bin包操作 + if self.server_status[BIN_UPGRADE][ENABLE_]: + code = self.ota_upgrade.start_upgrade_bin_event(self.module_type, self.uid, self.pk, reboot=self.reboot) + self.send_msg(message=dict(code=code), msg_type=BIN_UPGRADE) + + def _status_update(self, *args, **kwargs): + msg = "message" + em = kwargs.get(msg, False) + if em: + msg_type = em["msg_type"] + self.__upgrade_status_info[msg_type] = em[msg]["code"] + + def get_app_code(self): + return self.__upgrade_status_info[SH_UPGRADE] + + def prepare_before_start(self): + self.signal.connect(self._status_update, sender="anonymous") + + def start(self): + super().start() + # _thread.start_new_thread(self.__upgrade_bin, ()) + _thread.start_new_thread(self.__upgrade_sh, ()) + + def commit_log(self, message): + self.ota_upgrade.commit_log(self.pk, message) diff --git a/code/usr/bin/components/OTA.py b/code/usr/bin/components/OTA.py new file mode 100644 index 0000000..87a9fd0 --- /dev/null +++ b/code/usr/bin/components/OTA.py @@ -0,0 +1,499 @@ +import modem, ubinascii, net, uhashlib, request, ujson, app_fota, fota, utime, uos, log +from misc import Power +from unzip import UnZipUtils +from app_fota_download import update_download_stat, delete_update_file + +PROCESS_CODE = { + "DO_NOT_UPGRADE": 0, + "DOWNLOADING_FIRMWARE": 3, + "DOWNLOADED_NOTIFY_UPDATE": 4, + "DOWNLOAD_FAILED": 5, + "UPDATE_START": 6, + "UPGRADE_SUCCESS": 7, + "UPGRADE_FAILED": 8, +} +USR = "/usr/" +CONFIG_PATH = "ota_config.json" +MAIN_PY = "main.py" +BIN_MODE = "0" +APP_MODE = "1" +UPDATER_DIR = '/usr/.updater' +# 后续版本支持批量升级 +# BULK_APP_MODE = "2" +log.basicConfig(level=log.INFO) +ota_log = log.getLogger("ota") +fota_bin_file = "FotaFile.bin" +MODE_MAP = { + BIN_MODE: "_upgrade_fota_bin", + APP_MODE: "_upgrade_fota_sh", + # 后续支持BULK升级 + # BULK_APP_MODE: "_upgrade_fota_bulk_sh", +} +ZIP = ".zip" + + +class RET: + OK = "20000" + # 系统组件错误 + UPGRADE = "3001" + NOUPGRADE = "3002" + # 网络协议错误 + TOKENERR = "4001" + GETUPGRADEURLERR = "4002" + DOWNLOADERR = "4003" + PARAMSERR = "4004" + MODENOTEXIST = "4005" + FOTAVERIFAILD = "4006" + COMMIT_LOG_ERROR = "4007" + # 配置错误 + SAVECONFIGERR = "4021" + GETCONFIGERR = "4022" + NETERR = "4023" + REQERR = "4024" + WIOERR = "4025" + FWSTREAMERR = "4026" + RIOERR = "4027" + UNZIPERROR = "4028" + UNZIPFILEPROTOCOLERROR = "4029" + + +error_map = { + RET.OK: u"成功", + # 系统 + RET.UPGRADE: u"有升级计刿", + RET.NOUPGRADE: u"无升级计刿", + # 协议 + RET.TOKENERR: u"获取token失败", + RET.GETUPGRADEURLERR: u"获取升级地址失败", + RET.DOWNLOADERR: u"下载固件失败", + RET.PARAMSERR: u"参数获取失败", + RET.MODENOTEXIST: u"模式不存圿", + ### + RET.SAVECONFIGERR: u"保存配置错误", + RET.GETCONFIGERR: u"获取配置错误", + RET.NETERR: u"网络错误", + RET.WIOERR: u"文件写错诿", + RET.FWSTREAMERR: u"FOTA字节流作牿", + RET.RIOERR: u"文件读错诿", + RET.UNZIPERROR: u"文件解压失败", + RET.UNZIPFILEPROTOCOLERROR: u"压缩文件解析失败", +} + + +class OTAUtil(object): + @classmethod + def rm_upgrade_file(cls, file_name): + uos.remove(UPDATER_DIR + file_name) + delete_update_file(file_name) + + @classmethod + def add_upgrade_file(cls, url, file_name): + update_download_stat(url, USR + file_name, uos.stat(UPDATER_DIR + USR + file_name)[6]) + + +class UCloudOTA(object): + + def __init__(self, battery=100, url="http://220.180.239.212:8274/v1"): + self.imei = modem.getDevImei() + self.rsrp = net.csqQueryPoll() + self.url = url + self.module_type = "" + self.battery = battery + self.version = "" + self.uid = "" + self.pk = "" + self.cellId = "" + self.mnc = "" + self.mcc = "" + self.lac = "" + self.report_url = "/fota/status/report" + self.access_token = "" + self.upgrade_info = {} + self.bin_path = "/usr/FotaFile.bin" + self.ota_config_info = { + "version": "V1.0.0", + "flag": "0", + "fw_version": modem.getDevFwVersion() + } + self.info_map = { + APP_MODE: { + "reboot": True, + "uid": "", + "pk": "" + }, + BIN_MODE: { + "reboot": True, + "uid": "", + "pk": "" + } + } + + def _upgrade_fota(self, mode): + "升级fota bin" + try: + action = self.upgrade_info["action"] + url = self.upgrade_info["url"] + except Exception as e: + return RET.PARAMSERR + try: + if action: + self.report("DOWNLOADING_FIRMWARE") + if mode == APP_MODE: + if url.endswith(ZIP): + # APP 升级 + code = self._upgrade_fota_bulk_sh(url) + if code != RET.OK: + return code + else: + self._upgrade_fota_sh(url) + if self.info_map[APP_MODE]["reboot"]: + self.power_restart() + elif mode == BIN_MODE: + # fota 升级 + self._upgrade_fota_bin(url) + ota_log.info("self.info_map[BIN_MODE][reboot] === {}".format(self.info_map[BIN_MODE]["reboot"])) + if self.info_map[BIN_MODE]["reboot"]: + self.power_restart() + else: + # 模式 不匹酿 + return RET.MODENOTEXIST + self.report("DOWNLOADED_NOTIFY_UPDATE") + return RET.UPGRADE + else: + self.report("DO_NOT_UPGRADE") + return RET.NOUPGRADE + except Exception as e: + ota_log.error(e) + self.report(PROCESS_CODE[5]) + return RET.DOWNLOADERR + + def _upgrade_fota_bulk_sh(self, url): + """ + 批量升级 + :param url: + :return: + """ + zip_file = url.split("/")[-1] + fota = app_fota.new() + fota.download(url, USR + zip_file) + zip_util = UnZipUtils(dest_dir=UPDATER_DIR + USR) + try: + zip_util.set_data(zip_file) + except Exception as e: + ota_log.error(e) + return RET.UNZIPERROR + + try: + code = zip_util.run() + if not code: + return code + except Exception as e: + return RET.UNZIPFILEPROTOCOLERROR + for file_name in zip_util.file_name_list: + OTAUtil.add_upgrade_file(url, file_name) + OTAUtil.rm_upgrade_file(USR + zip_file) + self.report("DOWNLOADED_NOTIFY_UPDATE") + fota.set_update_flag() + try: + self.ota_config_info["version"] = self.upgrade_info["targetVersion"] + self.ota_config_info["flag"] = "1" + with open(USR + CONFIG_PATH, "w") as f: + f.write(ujson.dumps(self.ota_config_info)) + except Exception as e: + ota_log.info(e) + self.report("UPDATE_START") + return RET.OK + + def _upgrade_fota_sh(self, url): + """ + 升级单个脚本 + :param cover_file: 升级后覆盖的文件 + :return: + """ + zip_file = url.split("/")[-1] + fota = app_fota.new() + # 覆盖cover_file + cover_file = USR + zip_file[zip_file.find("_") + 1:] + fota.download(url, cover_file) + self.report("DOWNLOADED_NOTIFY_UPDATE") + fota.set_update_flag() + try: + self.ota_config_info["version"] = self.upgrade_info["targetVersion"] + self.ota_config_info["flag"] = "1" + with open(USR + CONFIG_PATH, "w") as f: + f.write(ujson.dumps(self.ota_config_info)) + except Exception as e: + ota_log.info(e) + self.report("UPDATE_START") + + def _upgrade_fota_bin(self, url): + self.report("DOWNLOADED_NOTIFY_UPDATE") + try: + r = request.get(url, sizeof=4096) + except Exception as e: + return RET.REQERR + if r.status_code == 200 or r.status_code == 206: + file_size = int(r.headers['Content-Length']) + fota_obj = fota() + count = 0 + try: + while True: + c = next(r.content) + length = len(c) + for i in range(0, length, 4096): + count += len(c[i:i + 4096]) + fota_obj.write(c[i:i + 4096], file_size) + except StopIteration: + r.close() + except Exception as e: + r.close() + return RET.FWSTREAMERR + else: + r.close() + res = fota_obj.verify() + if res != 0: + return RET.FOTAVERIFAILD + self.report("DOWNLOADED_NOTIFY_UPDATE") + return RET.OK + else: + return RET.REQERR + + def _upgrade_template(self, mode, module_type="", version=""): + """ + 开启固件脚本升级方弿 + :return: STATE + """ + self.module_type = modem.getDevFwVersion()[:5] if not module_type else module_type + + # 判斷模式 + if mode == APP_MODE: + state = self.register_upgrade_sh(version=version) + ota_log.info("the device app currently version is {}".format(self.version)) + elif mode == BIN_MODE: + state = self.register_upgrade_bin() + ota_log.info("the device fireware currently version is {}".format(self.version)) + else: + return RET.MODENOTEXIST + # 注册upgrade sh + if state != RET.OK: + return state + # 升级脚本 + state = self.get_upgrade_url(mode) + if state != RET.OK: + return state + # 升级脚本 + state = self._upgrade_fota(mode) + return state + + def get_token(self, prefix="/oauth/token", kwargs=None): + # 获取token + if net.csqQueryPoll() == 99 or net.csqQueryPoll() == -1: + return RET.NETERR + uri = self.url + prefix + try: + secret = ubinascii.hexlify(uhashlib.md5("QUEC" + str(self.imei) + "TEL").digest()) + secret = secret.decode() + uri = uri + "?imei=" + self.imei + "&" + "secret=" + secret + ota_log.info("uri = {}".format(uri)) + resp = request.get(uri) + json_data = resp.json() + ota_log.info("get_token = {}".format(json_data)) + self.access_token = json_data["data"]["access_Token"] + return RET.OK + except Exception as e: + ota_log.error("get_token error:{}".format(e)) + return RET.TOKENERR + + def report(self, code, msg=None): + data_info = { + "version": str(self.version), + "ver": "v1.0", + "imei": str(self.imei), + "code": PROCESS_CODE.get(code, 0), + "msg": str(msg) if msg else code + } + + ota_log.info(data_info) + + uri = self.url + self.report_url + + headers = {"access_token": self.access_token, "Content-Type": "application/json"} + try: + resp = request.post(uri, data=ujson.dumps(data_info), headers=headers) + return resp + except Exception as e: + return -1 + + def get_upgrade_url(self, mode, prefix="/fota/fw"): + ota_log.info("module_type == {}".format(self.module_type)) + params = "?" + "version=" + str(self.version) + "&" + "imei=" + str(self.imei) + "&" + "moduleType=" + str( + self.module_type) + "&" + "battery=" + str( + self.battery) + "&" + "rsrp=" + str( + self.rsrp) + "&" + "uid=" + str( + self.info_map[mode]["uid"]) + "&" + "pk=" + str( + self.info_map[mode]["pk"]) + ota_log.info(params) + uri = self.url + prefix + params + headers = dict(access_token=self.access_token) + try: + resp = request.get(uri, headers=headers) + json_data = resp.json() + ota_log.info("get_upgrade_url json data {}".format(json_data)) + if json_data["code"] == 200: + self.upgrade_info = json_data + return RET.OK + else: + ota_log.info("no upgrade plan for this device") + return RET.NOUPGRADE + except Exception as e: + ota_log.info("get_upgrade_url {}".format(e)) + return RET.GETUPGRADEURLERR + + def _register_upgrade(self, mode, version=""): + """ + 注册脚本升级 + :param version:版本叿 + :return: + """ + try: + list_dirs = uos.listdir(USR) + if CONFIG_PATH in list_dirs: + + with open(USR + CONFIG_PATH, "r") as f: + self.ota_config_info = ujson.loads(f.read()) + else: + if mode == APP_MODE: + # 应用检测版本号 + if version: + self.ota_config_info["version"] = version + with open(USR + CONFIG_PATH, "w") as f: + f.write(ujson.dumps(self.ota_config_info)) + except Exception as e: + ota_log.error("register_upgrade_sh = {}".format(e)) + # 读写config 错误 + return RET.GETCONFIGERR + + # 获取token + state = self.get_token() + if state != RET.OK: + return state + + try: + if mode == APP_MODE: + # app mode + self.version = self.ota_config_info["version"] + if self.ota_config_info["flag"] == "1": + # 升级成功 + self.report("UPGRADE_SUCCESS", self.ota_config_info["version"]) + self.ota_config_info["flag"] = "0" + with open(USR + CONFIG_PATH, "w") as f: + f.write(ujson.dumps(self.ota_config_info)) + elif mode == BIN_MODE: + # bin mode + self.version = modem.getDevFwVersion() + if self.ota_config_info["fw_version"] != self.version: + # OTA升级成功 + self.report("UPGRADE_SUCCESS", self.version) + self.ota_config_info['fw_version'] = self.version + with open(USR + CONFIG_PATH, "w") as f: + f.write(ujson.dumps(self.ota_config_info)) + else: + return RET.MODENOTEXIST + return RET.OK + except Exception as e: + return RET.SAVECONFIGERR + + def register_upgrade_sh(self, version=""): + return self._register_upgrade(APP_MODE, version=version) + + def register_upgrade_bin(self): + return self._register_upgrade(BIN_MODE) + + def start_upgrade_sh_event(self, module_type, uid, pk, version="", reboot=True): + """ + 开启固件脚本升级方弿 + :return: STATE + """ + self.info_map[APP_MODE]["reboot"] = reboot + self.info_map[APP_MODE]["uid"] = uid + self.info_map[APP_MODE]["pk"] = pk + return self._upgrade_template(APP_MODE, module_type=module_type, version=version) + + def start_upgrade_bin_event(self, module_type, uid, pk, reboot=True): + """ + 开启固件升级方弿 + :return: STATE + """ + # 升级脚本 + self.info_map[BIN_MODE]["reboot"] = reboot + self.info_map[BIN_MODE]["uid"] = uid + self.info_map[BIN_MODE]["pk"] = pk + return self._upgrade_template(BIN_MODE, module_type=module_type) + + def power_restart(self): + """ + 重启前的动作 + 重启设备 + :return: + """ + self.power_restart_before() + Power.powerRestart() + + def power_restart_before(self): + """ + 定义重启的动使 + :return: + """ + pass + + def commit_log(self, pk, message, battery=100, prefix="/fota/msg/report"): + v2_url = self.url[:-2] + "v2" + access_token = self.__get_v2_token(pk, v2_url) + if access_token == RET.TOKENERR: + return RET.TOKENERR + try: + cl = net.getCellInfo()[2][0] + data_info = { + "imei": self.imei, + "version": self.ota_config_info["version"], + "reportMsg": str(message), + "battery": battery, + "signalStrength": net.csqQueryPoll(), + "cellInfos": [ + { + "lac": cl[5], + "mcc": str(cl[2]), + "mnc": str(cl[3]), + "cid": cl[1], + "signalStrength": net.csqQueryPoll(), + } + ] + } + headers = { + "access_token": access_token, + "Content-Type": "application/json" + } + resp = request.post(self.url + prefix, data=ujson.dumps(data_info), + headers=headers) + return resp.json() + except Exception as e: + ota_log.info("commit_log exception {}".format(e)) + return RET.COMMIT_LOG_ERROR + + def __get_v2_token(self, pk, v2_url, prefix="/oauth/token"): + # 获取token + if net.csqQueryPoll() == 99 or net.csqQueryPoll() == -1: + return RET.NETERR + uri = v2_url + prefix + try: + uri = uri + "?imei=" + self.imei + "&" + "pk=" + pk + "&" + "grantType=1" + ota_log.info("uri = {}".format(uri)) + resp = request.get(uri) + json_data = resp.json() + ota_log.info("get_v2_token = {}".format(json_data)) + access_token = json_data["data"]["access_Token"] + return access_token + except Exception as e: + ota_log.error("get_v2_token error:{}".format(e)) + return RET.TOKENERR + diff --git a/code/usr/bin/components/abstract_service.py b/code/usr/bin/components/abstract_service.py new file mode 100644 index 0000000..649dee2 --- /dev/null +++ b/code/usr/bin/components/abstract_service.py @@ -0,0 +1,203 @@ +from usr.bin.components.blinker_min import Signal, signal +from event_message import Event, EventManager, EventMessageObject +from usr.bin.components.service_models import QMessageModel, LockMsgModel +import _thread + +ANY = "anonymous" + + +class AbstractService(object): + def __init__(self, sign): + self.__name = sign + + self.__signal = signal(sign) + + self.__event = Event(sign) + + self.__em = EventManager() + + self.__message_id = 0 + + self.__service_status = 0 + + self.__em.register_event(self.__event) + + self.__mode = 1 + + def set_mode(self, mode): + self.__mode = mode + + @property + def mode(self): + return self.__mode + + @property + def name(self): + return self.__name + + @property + def message_id(self): + """return self.next message id.""" + return self.__message_id + 1 + + @property + def signal(self): + """signal to get by this method""" + return self.__signal + + def ms_id_increase(self): + if self.__message_id > 99999: + self.__message_id = 0 + else: + self.__message_id += 1 + return self.message_id + + def _get_message(self, sender=None, msg_type=0xFF, message=None, callback=None, lock_msg=None): + """get message""" + if callback is not None and not callable(callback): + return None + ms_id = self.ms_id_increase() + qm = QMessageModel(ms_id=ms_id, msg_type=msg_type, message=message, sender=sender, from_event=self.__event.name, + lock_msg=lock_msg) + return qm + + def _callback(self, **kwargs): + """event callback""" + em = kwargs.get("event_message", False) + if em.msg: + msg = em.msg + try: + self.signal.send(em.msg.sender, message=msg()) + except Exception as e: + success = 0 + else: + success = 1 + resp_data = dict(message=msg(), success=success) + if em.callback is not None: + try: + em.callback(**resp_data) + except Exception as e: + pass + if msg.lock_msg is not None: + msg.lock_msg.msg = resp_data + msg.lock_msg.lock.release() + + def _clear(self, *args, **kwargs): + """ stop crontab task""" + self.__event.clear() + + def _add_default_handler(self): + """add default handler""" + self.add_handler(self._callback) + + def register_event(self): + """achieve by class extend AbstractService""" + pass + + def prepare_before_start(self): + """prepare before start action""" + pass + + def prepare_before_stop(self): + """prepare after start action""" + pass + + def start_crontab(self, *args, **kwargs): + """achieve by timer task""" + pass + + def add_handler(self, handler): + """ + support for scan addition and manual sword + :param handler: deal handler + :return: + """ + self.__event.add_handler(handler) + return handler + + def add_handler_via(self): + """ + Support for multiple annotations + :return: + """ + + def decorator(fn): + self.add_handler(fn) + return fn + + return decorator + + def send_msg_async(self, msg_type=0xFF, message=None, callback=None, sender=None): + if self.status(): + qm = self._get_message(sender=sender, msg_type=msg_type, message=message, callback=callback) + self.__event.post(qm, callback=callback) + else: + return 0 + + def send_msg_sync(self, msg_type=0xFF, message=None, callback=None, sender=None): + if self.status(): + lock = _thread.allocate_lock() + lock_msg = LockMsgModel(lock) + qm = self._get_message(sender=sender, msg_type=msg_type, message=message, callback=callback, + lock_msg=lock_msg) + self.__event.post(qm, callback=callback) + lock_msg.lock.acquire() + msg = lock_msg.msg + del lock_msg + return msg + else: + return 0 + + def send_msg(self, msg_type=0xFF, message=None, callback=None, sender=None, mode=None): + mode_pattern = self.mode + if mode is not None: + mode_pattern = mode + if mode_pattern: + return self.send_msg_async(msg_type=msg_type, message=message, callback=callback, sender=sender) + else: + return self.send_msg_sync(msg_type=msg_type, message=message, callback=callback, sender=sender) + + def _component_start(self): + """start components""" + if not self.__service_status: + self.register_event() + self._add_default_handler() + self.prepare_before_start() + self.start_crontab() + + def start(self): + """ + start the service + :return: 1 or 0 + """ + self._component_start() + self.__service_status = self.__em.start() + return self.__service_status + + def status(self): + """ + status of service + :return: + """ + return self.__em.status + + def stop(self): + """ + stop the service + :return: + """ + self.prepare_before_stop() + self.__service_status = self.__em.stop() + return self.__service_status + + def close(self): + self._clear() + + def subscribe(self, cb, sender=ANY): + self.signal.connect(cb, sender=sender) + + def unsubscribe(self, cb, sender=ANY): + self.signal.disconnect(cb, sender=sender) + + def publish(self, msg, sender=ANY, msg_type=0XFF): + self.send_msg(message=msg, msg_type=msg_type, sender=sender) diff --git a/code/usr/bin/components/blinker_min.py b/code/usr/bin/components/blinker_min.py new file mode 100644 index 0000000..69a118b --- /dev/null +++ b/code/usr/bin/components/blinker_min.py @@ -0,0 +1,541 @@ +# -*- coding: utf-8; fill-column: 76 -*- + +import gc + +__all__ = ["signal", "Signal"] + + +class attrgetter: + __slots__ = ('_attrs', '_call') + + def __init__(self, attr, *attrs): + if not attrs: + if not isinstance(attr, str): + raise TypeError('attribute name must be a string') + self._attrs = (attr,) + names = attr.split('.') + + def func(obj): + for name in names: + obj = getattr(obj, name) + return obj + + self._call = func + else: + self._attrs = (attr,) + attrs + getters = tuple(map(attrgetter, self._attrs)) + + def func(obj): + return tuple(getter(obj) for getter in getters) + + self._call = func + + def __call__(self, obj): + return self._call(obj) + + def __repr__(self): + return '%s.%s(%s)' % (self.__class__.__module__, + self.__class__.__qualname__, + ', '.join(map(repr, self._attrs))) + + +class ReferenceType(object): + + def __new__(cls, *args, **kwargs): + return super(ReferenceType, cls).__new__(cls) + + def __call__(self, *args, **kwargs): + return + + def __init__(self, *args, **kwargs): + self.params = dict() + + def __setitem__(self, key, value): + self.__dict__[key] = value + + def __getitem__(self, key): + if key not in self.__dict__: + self.__dict__[key] = None + return self.__dict__[key] + + __callback__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default + + +ref = ReferenceType + + +class KeyedRef(ref): + __slots__ = "key", + + def __new__(type, ob, callback, key): + self = ref.__new__(type, ob, callback) + self.key = key + return self + + def __init__(self, ob, callback, key): + super().__init__(ob, callback) + + +class WeakValueDictionary(dict): + def __init__(self, dict=None): + self.data = {} + + def remove(k, selfref=ref(self)): + self = selfref() + if self is not None: + if self._iterating: + self._pending_removals.append(k) + else: + del self.data[k] + + self._remove = remove + self._pending_removals = [] + self._iterating = set() + self._dirty_len = False + if dict is not None: + self.update(dict) + + def _commit_removals(self): + gc.collect() + + def __getitem__(self, key): + if self._pending_removals: + self._commit_removals() + o = self.data[key]() + if o is None: + raise KeyError(key) + else: + return o + + def __setitem__(self, key, value): + if self._pending_removals: + self._commit_removals() + self.data[key] = KeyedRef(value, self._remove, key) + + def get(self, key, default=None): + if self._pending_removals: + self._commit_removals() + try: + wr = self.data[key] + except KeyError: + return default + else: + o = wr() + if o is None: + return default + else: + return o + + def update(self, other=None, **kwargs): + if self._pending_removals: + self._commit_removals() + d = self.data + if other is not None: + if not hasattr(other, "items"): + other = dict(other) + for key, o in other.items(): + d[key] = KeyedRef(o, self._remove, key) + for key, o in kwargs.items(): + d[key] = KeyedRef(o, self._remove, key) + + +try: + callable +except NameError: + def callable(object): + return hasattr(object, '__call__') + +get_self = attrgetter('__self__') +get_func = attrgetter('__func__') + + +class BoundMethodWeakref(object): + _all_instances = WeakValueDictionary() + + def __new__(cls, target, on_delete=None, *arguments, **named): + key = cls.calculate_key(target) + current = cls._all_instances.get(key) + if current is not None: + current.deletion_methods.append(on_delete) + return current + else: + base = super(BoundMethodWeakref, cls).__new__(cls) + cls._all_instances[key] = base + base.__init__(target, on_delete, *arguments, **named) + return base + + def __init__(self, target, on_delete=None): + def remove(self=self): + methods = self.deletion_methods[:] + del self.deletion_methods[:] + try: + del self.__class__._all_instances[self.key] + except KeyError: + pass + for function in methods: + try: + if callable(function): + function(self) + except Exception as e: + print('Exception during saferef %s ' + 'cleanup function %s: %s' % (self, function, e)) + + self.deletion_methods = [on_delete] + self.key = self.calculate_key(target) + im_self = get_self(target) + im_func = get_func(target) + self.weak_self = ref(im_self, remove) + self.weak_func = ref(im_func, remove) + self.self_name = str(im_self) + self.func_name = str(im_func.__name__) + + def calculate_key(cls, target): + return (id(get_self(target)), id(get_func(target))) + + calculate_key = classmethod(calculate_key) + + def __str__(self): + return "%s(%s.%s)" % ( + self.__class__.__name__, + self.self_name, + self.func_name, + ) + + __repr__ = __str__ + + def __call__(self): + target = self.weak_self() + if target is not None: + function = self.weak_func() + if function is not None: + return function.__get__(target) + return None + + +class defaultdict(object): + params = dict() + + def __init__(self, default_factory=None, *a, **kw): + super().__init__(*a, **kw) + self.default_factory = default_factory + + def __getitem__(self, key): + try: + result = self.params[key] + return result + except KeyError: + return self.__missing__(key) + + def __setitem__(self, key, value): + try: + self.params[key] = set(value) + except TypeError: + return "Type error" + + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + self.params[key] = value = self.default_factory() + return value + + def __reduce__(self): + if self.default_factory is None: + args = tuple() + else: + args = self.default_factory, + return type(self), args, None, None, self.items() + + def copy(self): + return self.__copy__() + + def pop(self, key, default): + return self.params.pop(key, default) + + def values(self): + return self.params.values() + + def items(self): + return self.params.items() + + def clear(self): + return self.params.clear() + + def __copy__(self): + return type(self)(self.default_factory, self) + + def __repr__(self): + return 'defaultdict(%s, %s)' % (self.default_factory, + dict.__repr__(self)) + + def __contains__(self, key): + if key in self.params: + return True + else: + return False + + +class _symbol(object): + + def __init__(self, name): + self.__name__ = self.name = name + + def __reduce__(self): + return symbol, (self.name,) + + def __repr__(self): + return self.name + + +_symbol.__name__ = 'symbol' + + +class symbol(object): + symbols = {} + + def __new__(cls, name): + try: + return cls.symbols[name] + except KeyError: + return cls.symbols.setdefault(name, _symbol(name)) + + +try: + text = (str, unicode) +except NameError: + text = str + + +def hashable_identity(obj): + if hasattr(obj, '__func__'): + return (id(obj.__func__), id(obj.__self__)) + elif isinstance(obj, text): + return obj + else: + return id(obj) + + +WeakTypes = (ref, BoundMethodWeakref) + + +class annotatable_weakref(ref): + """A weakref.ref that supports custom instance attributes.""" + + +def reference(object, callback=None, **annotations): + if callable(object): + weak = callable_reference(object, callback) + else: + weak = annotatable_weakref(object, callback) + for key, value in annotations.items(): + setattr(weak, key, value) + return weak + + +def callable_reference(object, callback=None): + if hasattr(object, 'im_self') and object.im_self is not None: + return BoundMethodWeakref(target=object, on_delete=callback) + elif hasattr(object, '__self__') and object.__self__ is not None: + return BoundMethodWeakref(target=object, on_delete=callback) + return annotatable_weakref(object, callback) + + +ANY = symbol('ANY') +ANY.__doc__ = 'Token for "any sender".' +ANY_ID = 0 + + +class Signal(object): + ANY = ANY + + def receiver_connected(self): + return Signal(doc="Emitted after a receiver connects.") + + def receiver_disconnected(self): + return Signal(doc="Emitted after a receiver disconnects.") + + def __init__(self, doc=None): + self.receivers = {} + self._by_receiver = defaultdict(default_factory=set) + self._by_sender = defaultdict(default_factory=set) + self._weak_senders = {} + + def __repr__(self): + return "<%s at %#x>" % (self.__class__.__name__, id(self)) + + def connect(self, receiver, sender=ANY, weak=False): + receiver_id = hashable_identity(receiver) + weak = False + receiver_ref = receiver + if sender is ANY: + sender_id = ANY_ID + else: + sender_id = hashable_identity(sender) + self.receivers.setdefault(receiver_id, receiver_ref) + self._by_sender[sender_id].add(receiver_id) + self._by_receiver[receiver_id].add(sender_id) + del receiver_ref + + if sender is not ANY and sender_id not in self._weak_senders: + try: + sender_ref = reference(sender, self._cleanup_sender) + sender_ref.sender_id = sender_id + except TypeError: + pass + else: + self._weak_senders.setdefault(sender_id, sender_ref) + del sender_ref + + if ('receiver_connected' in self.__dict__ and + self.receiver_connected.receivers): + try: + self.receiver_connected.send(self, + receiver=receiver, + sender=sender, + weak=weak) + except: + self.disconnect(receiver, sender) + raise + if receiver_connected.receivers and self is not receiver_connected: + try: + receiver_connected.send(self, + receiver_arg=receiver, + sender_arg=sender, + weak_arg=weak) + except: + self.disconnect(receiver, sender) + raise + return receiver + + def connect_via(self, sender=ANY, weak=False): + def decorator(fn): + self.connect(fn, sender, weak) + return fn + + return decorator + + def send(self, *sender, **kwargs): + if len(sender) == 0: + sender = None + elif len(sender) > 1: + raise TypeError('send() accepts only one positional argument, ' + '%s given' % len(sender)) + else: + sender = sender[0] + if not self.receivers: + return [] + else: + return [(receiver, receiver(sender, **kwargs)) + for receiver in self.receivers_for(sender)] + + def has_receivers_for(self, sender): + if not self.receivers: + return False + if self._by_sender[ANY_ID]: + return True + if sender is ANY: + return False + return hashable_identity(sender) in self._by_sender + + def receivers_for(self, sender): + if self.receivers: + sender_id = hashable_identity(sender) + if sender_id in self._by_sender: + ids = (self._by_sender[ANY_ID] | + self._by_sender[sender_id]) + else: + ids = self._by_sender[ANY_ID].copy() + for receiver_id in ids: + receiver = self.receivers.get(receiver_id) + if receiver is None: + continue + if isinstance(receiver, WeakTypes): + strong = receiver() + if strong is None: + self._disconnect(receiver_id, ANY_ID) + continue + receiver = strong + yield receiver + + def disconnect(self, receiver, sender=ANY): + if sender is ANY: + sender_id = ANY_ID + else: + sender_id = hashable_identity(sender) + receiver_id = hashable_identity(receiver) + self._disconnect(receiver_id, sender_id) + + if ('receiver_disconnected' in self.__dict__ and + self.receiver_disconnected.receivers): + self.receiver_disconnected.send(self, + receiver=receiver, + sender=sender) + + def _disconnect(self, receiver_id, sender_id): + if sender_id == ANY_ID: + if self._by_receiver.pop(receiver_id, False): + for bucket in self._by_sender.values(): + bucket.discard(receiver_id) + self.receivers.pop(receiver_id, None) + else: + self._by_sender[sender_id].discard(receiver_id) + self._by_receiver[receiver_id].discard(sender_id) + + def _cleanup_receiver(self, receiver_ref): + self._disconnect(receiver_ref.receiver_id, ANY_ID) + + def _cleanup_sender(self, sender_ref): + sender_id = sender_ref.sender_id + assert sender_id != ANY_ID + self._weak_senders.pop(sender_id, None) + for receiver_id in self._by_sender.pop(sender_id, ()): + self._by_receiver[receiver_id].discard(sender_id) + + def _cleanup_bookkeeping(self): + for mapping in (self._by_sender, self._by_receiver): + for _id, bucket in list(mapping.items()): + if not bucket: + mapping.pop(_id, None) + + def _clear_state(self): + self._weak_senders.clear() + self.receivers.clear() + self._by_sender.clear() + self._by_receiver.clear() + + +receiver_connected = Signal("""\ +Sent by a :class:`Signal` after a receiver connects. +""") + + +class NamedSignal(Signal): + + def __init__(self, name, doc=None): + Signal.__init__(self, doc) + self.name = name + + def __repr__(self): + base = Signal.__repr__(self) + return "%s; %r>" % (base[:-1], self.name) + + +class Namespace(dict): + + def signal(self, name, doc=None): + try: + return self[name] + except KeyError: + return self.setdefault(name, NamedSignal(name, doc)) + + +signal = Namespace().signal +if __name__ == '__main__': + dice_roll = signal('dice_roll') + + + def odd_subscriber(sender, **kwargs): + print("Observed dice roll %r. %r" % (sender, kwargs)) + + + dice_roll.connect(odd_subscriber, sender=0x03) + result = dice_roll.send(3) diff --git a/code/usr/bin/components/monitor.py b/code/usr/bin/components/monitor.py new file mode 100644 index 0000000..01f811e --- /dev/null +++ b/code/usr/bin/components/monitor.py @@ -0,0 +1,96 @@ +__all__ = [ + "ServiceMonitor", +] + +from misc import Power +import osTimer + + +class MonitorInterface(object): + def start(self): + """start""" + + def stop(self): + """stop""" + + def status(self): + """status""" + + +class ServiceMonitor(MonitorInterface): + THRESHOLD = 3 + + def __init__(self, service): + self.service = service + self.__failed_count = 0 + self.__level = 0 + self.__timer = None + self.__ping_count = 0 + self.__exception_handlers = {} + self.service.signal.connect(self.ping_handler, sender=0xFE) + + def set_level(self, level): + if self.__level != level: + self.__level = level + + def __timer_handle(self, *args): + """定时器调度""" + if self.__ping_count: + self.__failed_count += 1 + self.__ping_count = self.__ping_count + 1 + self.service.signal.send(0xFE) + if self.__failed_count: + self.__failed_handle() + + def set_exception_handlers(self, handler): + if handler is not None: + self.__exception_handlers = handler + + def __failed_handle(self): + """失败处理""" + try: + for k, v in self.__exception_handlers.items(): + if k == "reboot": + if v.get("failCount") == self.__failed_count: + # 关机 + Power.powerDown() + elif k == "stop": + if v.get("failCount") == self.__failed_count: + # 停止服务 + self.stop() + else: + continue + except Exception as e: + print(e) + + def ping_handler(self, *args, **kwargs): + self.__ping_count = 0 + + def start(self): + if self.__level != self.THRESHOLD: + self.service.start() + if self.__timer is None: + self.__timer = osTimer() + self.__timer.start(15000, 1, self.__timer_handle) + return self.status() + + def status(self): + return self.service.status() + + def clear(self): + self.__failed_count = 0 + self.__level = 0 + if self.__timer is not None: + self.__timer.stop() + self.__timer.delete() + self.__timer = None + self.__ping_count = 0 + + def stop(self): + if self.__level != self.THRESHOLD: + self.service.stop() + self.clear() + return self.status + + def __call__(self, *args, **kwargs): + return self.service diff --git a/code/usr/bin/components/service_models.py b/code/usr/bin/components/service_models.py new file mode 100644 index 0000000..c500ea7 --- /dev/null +++ b/code/usr/bin/components/service_models.py @@ -0,0 +1,72 @@ +__all__ = [ + "QMessageModel" +] + + +class ModelDTO(object): + def model_to_dict(self, *args, **kwargs): + pass + + def __call__(self, *args, **kwargs): + return self.model_to_dict(*args, **kwargs) + + +class QMessageModel(ModelDTO): + def __init__(self, ms_id=None, msg_type=0xFF, message=None, sender=None, from_event=None, lock_msg=None): + self.__msg_type = msg_type + self.__message_id = ms_id + self.__message = message + self.__sender = "anonymous" if sender is None else sender + self.__from_event = from_event + self.__lock_msg = lock_msg + + @property + def msg_type(self): + return self.__msg_type + + @property + def lock_msg(self): + return self.__lock_msg + + @property + def message_id(self): + return self.__message_id + + @property + def sender(self): + return self.__sender + + @property + def message(self): + return self.__message + + @property + def from_event(self): + return self.__from_event + + def model_to_dict(self): + return dict(message_id=self.message_id, msg_type=self.msg_type, message=self.message, sender=self.sender, + from_event=self.from_event) + + +class LockMsgModel(ModelDTO): + """传输对象""" + + def __init__(self, lock): + self.__lock = lock + self.__msg = dict(message=None) + + @property + def msg(self): + return self.__msg + + @msg.setter + def msg(self, msg): + self.__msg = msg + + @property + def lock(self): + return self.__lock + + def model_to_dict(self, *args, **kwargs): + return dict(lock=self.__lock, msg=self.__msg) diff --git a/code/usr/bin/exception_service.py b/code/usr/bin/exception_service.py new file mode 100644 index 0000000..984c87a --- /dev/null +++ b/code/usr/bin/exception_service.py @@ -0,0 +1,32 @@ +from usr.bin.components.abstract_service import AbstractService +from usr.bin.components.monitor import ServiceMonitor +from usr.utils.service_utils import Singleton + + +class ExceptionServiceMonitor(ServiceMonitor): + @staticmethod + def create_monitor(config=None): + esm = ExceptionServiceMonitor(ExceptionService()) + if config is not None: + esm.set_exception_handlers(config.get('exceptionHandlers', None)) + return esm + + +@Singleton +class ExceptionService(AbstractService): + + def __init__(self): + super().__init__("EXCEPTION") + self.error_message = [] + + def handler_error(self, *args, **kwargs): + msg = "message" + em = kwargs.get(msg, False) + if em: + print(em[msg]) + + def prepare_before_start(self): + self.signal.connect(self.handler_error, sender="anonymous") + + def __call__(self, message): + self.send_msg(message=message) diff --git a/code/usr/bin/guard.py b/code/usr/bin/guard.py new file mode 100644 index 0000000..449c376 --- /dev/null +++ b/code/usr/bin/guard.py @@ -0,0 +1,232 @@ +from usr.utils.JsonParserUtils import JsonParser +from usr.bin.exception_service import ExceptionService +from usr.utils.service_utils import Singleton +from usr.bin.components.monitor import MonitorInterface +from usr.bin.log_service import LogAdapter +import uos as os + +__GROUP_ID = "qpy.quectel.com" +__ARTIFACT_ID = "qpy-framework" +__VERSION = "1.0.0.RELEASE" + + +def version(): + return {"GROUP_ID": __GROUP_ID, "artifact_id": __ARTIFACT_ID, "VERSION": __VERSION} + + +@Singleton +class Guard(object): + def __init__(self): + self.monitor_service = set() + self.timer = None + self.__status = 0 + + def register_monitor(self, monitor): + self.monitor_service.add(monitor) + + def start(self): + for monitor in self.monitor_service: + monitor.start() + self.__status = 1 + + def reload_level(self): + for monitor in self.monitor_service: + monitor.set_level(0) + monitor.start() + self.__status = 1 + + def stop(self): + for monitor in self.monitor_service: + monitor.stop() + self.__status = 0 + + def status(self): + return self.__status + + def upgrade(self): + pass + + def monitor_map(self): + return {m.service.name: m.service for m in self.monitor_service} + + +@Singleton +class GuardContext(object): + + def __init__(self): + self.__guard = Guard() + self.system_config = {} + self.service_config = {} + self.config_map = { + "/usr/etc/system_config": self.system_config, + "/usr/etc/app_config": self.service_config + } + self.config_parser = JsonParser() + self.monitor_map = dict() + self.error_handler = ExceptionService() + self.error_handler.start() + + def servers(self): + return self.monitor_map.copy() + + def get_server(self, name): + return self.monitor_map[name]() + + def stop_server(self, name): + return self.monitor_map[name].stop() + + @staticmethod + def get_logger(name): + return LogAdapter(name) + + @staticmethod + def path_exist(check_path): + try: + os.stat(check_path) + except Exception as e: + return 0 + else: + return 1 + + def load_config_definitions(self): + # load config + # root path /usr/etc + root_path = "/usr/etc/" + stat = self.path_exist(root_path) + if not stat: + return + self.load_configs(root_path) + + def load_configs(self, root_path): + + # system_path of service /usr/etc/system_config + stat = self.path_exist(root_path) + if not stat: + self.error_handler("[ WARN ] {} path is not exist".format(root_path)) + return True + for par_path in os.listdir(root_path): + abs_path = root_path + par_path + for sub_path in os.listdir(abs_path): + truth_path = abs_path + "/" + sub_path + rep_d = self.config_parser.parse(truth_path) + if rep_d["status"] == 0: + # 错误日志收集 + self.error_handler("[ WARN ] read {} status {}".format(truth_path, 0)) + else: + self.config_map[abs_path][sub_path] = rep_d["data"] + + def create_monitors(self): + # 创建monitors + monitor_map = dict() + monitor_map.update(self.create_system_monitors()) + monitor_map.update(self.create_app_monitors()) + self.monitor_map = monitor_map + + def start(self): + # 启动守卫 拉起所有的服务 + self.__guard.start() + + def register_monitors(self): + # 注册monitor + for k, v in self.monitor_map.items(): + self.__guard.register_monitor(v) + + def register_monitor(self, k, v): + flag = False + if isinstance(v, MonitorInterface) and isinstance(k, str): + self.monitor_map[k] = v + self.__guard.register_monitor(v) + flag = not flag + return flag + + def refresh(self): + self.load_config_definitions() + # 初始化monitor + self.create_monitors() + self.register_monitors() + self.start() + + def reload(self): + # 刷新通知 + self.__guard.reload_level() + + def create_system_monitors(self): + monitor_map = dict() + try: + from usr.bin.net_service import NetServiceMonitor + NET = "net" + net_monitor = NetServiceMonitor.create_monitor(self.system_config.get(NET, None)) + monitor_map[NET] = net_monitor + print("[ OK ] create sys monitor net service") + except Exception as e: + # 异常重定向 + self.error_handler("[ FAILED ] load net monitor error reason:{}".format(e)) + + try: + from usr.bin.log_service import LogServiceMonitor + LOG = "log" + log_monitor = LogServiceMonitor.create_monitor(self.system_config.get(LOG, None)) + monitor_map[LOG] = log_monitor + print("[ OK ] create sys monitor log service") + except Exception as e: + self.error_handler("[ FAILED ] load log monitor error reason:{}".format(e)) + return monitor_map + + def create_app_monitors(self): + monitor_map = dict() + try: + from usr.bin.media_service import MediaServiceMonitor + MEDIA = "media" + md_monitor = MediaServiceMonitor.create_monitor(self.service_config.get(MEDIA, None)) + if md_monitor is not None: + monitor_map[MEDIA] = md_monitor + else: + raise Exception("media service load error") + print("[ OK ] create app monitor media service") + except Exception as e: + # 异常重定向 + self.error_handler("[ FAILED ] load exception monitor error reason: [{}]".format(e)) + + try: + from usr.bin.exception_service import ExceptionServiceMonitor + EXCEPTION = "exception" + ex_monitor = ExceptionServiceMonitor.create_monitor(self.service_config.get(EXCEPTION, None)) + if ex_monitor is not None: + monitor_map[EXCEPTION] = ex_monitor + else: + raise Exception("exception service load error") + print("[ OK ] create app monitor exception service") + except Exception as e: + # 异常重定向 + self.error_handler("[ FAILED ] load exception monitor error reason:[{}]".format(e)) + + try: + from usr.bin.cloud_service import CloudServiceMonitor + CLOUD = "cloud" + cd_monitor = CloudServiceMonitor.create_monitor(self.service_config.get(CLOUD, None)) + if cd_monitor is not None: + monitor_map[CLOUD] = cd_monitor + else: + raise Exception("cloud service load error") + print("[ OK ] create app monitor cloud service") + except Exception as e: + # 异常重定向 + self.error_handler("[ FAILED ] load cloud monitor error reason:[{}]".format(e)) + + try: + from usr.bin.pm_service import PMServiceMonitor + PM = "pm" + pm_monitor = PMServiceMonitor.create_monitor(self.service_config.get(PM, None)) + if pm_monitor is not None: + monitor_map[PM] = pm_monitor + else: + raise Exception("pm service load error") + print("[ OK ] create app monitor pm service") + except Exception as e: + # 异常重定向 + self.error_handler("[ FAILED ] load pm monitor error reason:[{}]".format(e)) + + return monitor_map + + def version(self): + return version() diff --git a/code/usr/bin/log_service.py b/code/usr/bin/log_service.py new file mode 100644 index 0000000..51bf881 --- /dev/null +++ b/code/usr/bin/log_service.py @@ -0,0 +1,232 @@ +from usr.bin.components.abstract_service import AbstractService +from usr.utils.service_utils import Singleton +from usr.utils.resolver import TimeResolver +from usr.bin.components.monitor import ServiceMonitor +from machine import UART +import uos + +LOGGER = "LOG" + + +class LOG_LV: + DEBUG = "DEBUG" + INFO = "INFO" + WARNING = "WARNING" + ERROR = "ERROR" + CRITICAL = "CRITICAL" + + +class AbstractLogOutputUtil(object): + def open(self): + pass + + def output(self, message, **kwargs): + pass + + def close(self): + pass + + def __call__(self, message, **kwargs): + self.output(message, **kwargs) + + +@Singleton +class UartLogOutputUtil(AbstractLogOutputUtil): + def __init__(self, UARTn=UART.UART2): + self.uart = UART(UARTn, 115200, 8, 0, 1, 0) + + def output(self, message, **kwargs): + self.uart.write(message) + + +@Singleton +class PrintLogOutputUtil(AbstractLogOutputUtil): + def output(self, message, **kwargs): + print(message) + + +@Singleton +class MqttLogOutputUtil(AbstractLogOutputUtil): + + def __init__(self): + super().__init__() + + def output(self, message, **kwargs): + pass + + +@Singleton +class ConsoleLogOutputUtil(AbstractLogOutputUtil): + def __init__(self): + super().__init__() + + def output(self, message, **kwargs): + pass + + +@Singleton +class FileLogOutputUtil(AbstractLogOutputUtil): + def __init__(self): + super().__init__() + self.file_name = "log.txt" + self.abs_path = "/usr/log/" + self.content = [] + self.f = None + + def open(self): + if "log" not in uos.listdir("/usr"): + uos.mkdir(self.abs_path) + self.f = open(self.abs_path + self.file_name, "w+") + + def output(self, message, **kwargs): + self.open() + if len(self.content) > 10: + self.close() + + def close(self): + self.f.close() + self.f = None + + +class AbstractFormatUtil(object): + def format(self, *args, **kwargs): + pass + + +class LogFormatUtil(AbstractFormatUtil): + def format(self, *args, **kwargs): + log_format = "{} {} [{}] - {}\n".format(args[0], args[1], args[2], args[3]) + return log_format + + +class LogServiceMonitor(ServiceMonitor): + + @staticmethod + def create_monitor(config=None): + log_service = LogService() + if config is not None: + level = config.get('level') + log_service.set_level(level) + lsm = LogServiceMonitor(log_service) + if config is not None: + lsm.set_exception_handlers(config.get('exceptionHandlers', None)) + return lsm + + +@Singleton +class LogService(AbstractService): + """ + default log is async queue + you can set + """ + + def __init__(self): + super().__init__(LOGGER) + self.__reporter = PrintLogOutputUtil() + self.__tr = TimeResolver() + self.format_util = LogFormatUtil() + self.__level_map = { + LOG_LV.DEBUG: 0, + LOG_LV.INFO: 1, + LOG_LV.WARNING: 2, + LOG_LV.ERROR: 3, + LOG_LV.CRITICAL: 4 + } + self.low_level = 0 + + def __set_report(self, report): + if isinstance(report, AbstractLogOutputUtil): + self.__reporter = report + + def set_output(self, out_obj): + if isinstance(out_obj, AbstractLogOutputUtil): + self.__reporter.close() + self.__reporter = out_obj + self.__reporter.open() + else: + self.log_send(self.name, LOG_LV.ERROR, '"{}" is not extend AbstractLogOutputUtil'.format(out_obj)) + raise Exception('"{}" is not extend AbstractLogOutputUtil'.format(out_obj)) + + def set_level(self, level): + if level in self.__level_map: + self.low_level = self.__level_map[level] + else: + self.low_level = 0 + + def log_send(self, sign, level, msg, mode=1): + """send log deal""" + if self.mode is not None: + mode = self.mode + if self.__level_map[level] >= self.low_level: + if mode: + self.send_msg_async(message=self.format_msg(sign, level, msg)) + else: + self.send_msg_sync(message=self.format_msg(sign, level, msg)) + + def format_msg(self, sign, level, msg): + """ + format msg + year-month-day hour-minute-second weekday service [level] - message + """ + return self.format_util.format(self.__tr.resolver(), sign, level, msg) + + def output_msg(self, *args, **kwargs): + msg = "message" + em = kwargs.get(msg, False) + if em: + self.__reporter.output(em[msg]) + + def prepare_before_stop(self): + self.__reporter.close() + + def prepare_before_start(self): + self.signal.connect(self.output_msg, sender="anonymous") + self.__reporter.open() + + +class LogAdapter(object): + """ + log adapter mode + mode used : adapter proxy single LogService + + """ + + def __init__(self, name, enable=1): + self.log_service = LogService() + self.name = name + self.enable = enable + self.mode = 1 + self.tag = None + + def get_tag(self): + if self.tag is None: + return self.name + else: + return self.tag + + def critical(self, msg): + if self.enable: + self.log_service.log_send(self.name, LOG_LV.CRITICAL, msg, self.mode) + + def debug(self, msg): + if self.enable: + self.log_service.log_send(self.name, LOG_LV.DEBUG, msg, self.mode) + + def info(self, msg): + if self.enable: + self.log_service.log_send(self.name, LOG_LV.INFO, msg, self.mode) + + def warning(self, msg): + if self.enable: + self.log_service.log_send(self.name, LOG_LV.WARNING, msg, self.mode) + + def error(self, msg): + if self.enable: + self.log_service.log_send(self.name, LOG_LV.ERROR, msg, self.mode) + + +if __name__ == '__main__': + net_ser = LogAdapter("Net") + ls = LogService() + ls.start() + net_ser.debug("111") diff --git a/code/usr/bin/media_service.py b/code/usr/bin/media_service.py new file mode 100644 index 0000000..bcdd26e --- /dev/null +++ b/code/usr/bin/media_service.py @@ -0,0 +1,98 @@ +from usr.bin.components.abstract_service import AbstractService +from usr.bin.components.monitor import ServiceMonitor +import audio +import utime + +MEDIA = "MEDIA" +MEDIA_TYPE_MAP = { + "AUDIO": 0, + "TTS": 1 +} + + +class MediaServiceMonitor(ServiceMonitor): + + @staticmethod + def create_monitor(config=None): + media_service = MediaService() + if config is not None: + try: + if config.get('enable', True): + mode = config["params"]["mode"] + return MediaServiceMonitor(media_service) + except Exception as e: + # 这里异常重定向 + return None + msm = MediaServiceMonitor(media_service) + if config is not None: + msm.set_exception_handlers(config.get('exceptionHandlers', None)) + return msm + + +class MediaService(AbstractService): + + def __init__(self, device=0): + super().__init__(MEDIA) + self.__tts = audio.TTS(device) + self.__audio = audio.Audio(device) + + def set_tts(self, mode): + self.__tts = audio.TTS(mode) + + def set_audio(self, mode): + self.__audio = audio.Audio(mode) + + def set_pa(self, pa): + return self.__audio.set_pa(pa) + + def set_mode(self, mode): + if mode in range(0x03): + self.set_audio(mode) + self.set_tts(mode) + else: + raise Exception("mode {} must in mid of [0,1,2]".format(mode)) + + @property + def tts(self): + return self.__tts + + @property + def audio(self): + return self.__audio + + def _play(self, sender, **kwargs): + msg_type = kwargs["message"]['msg_type'] + msg = kwargs["message"]['message'] + # 策略 + ret = self.__start_play(msg_type, msg) + + while ret == -2: + print(ret, msg) + ret = self.__start_play(msg_type, msg) + utime.sleep(1) + + def __start_play(self, msg_type, msg): + if msg_type == MEDIA_TYPE_MAP["TTS"]: + ret = self.__tts.play(msg["priority"], msg["breakin"], msg["mode"], msg['play_data']) + else: + ret = self.__audio.play(msg["priority"], msg["breakin"], msg['play_data']) + return ret + + def tts_play(self, priority=4, breakin=0, mode=2, play_data="", sender=None): + message = dict(priority=priority, breakin=breakin, mode=mode, play_data=play_data) + self.send_msg(msg_type=MEDIA_TYPE_MAP["TTS"], sender=sender, message=message) + + def audio_play(self, priority=4, breakin=0, play_data="", sender=None): + message = dict(priority=priority, breakin=breakin, play_data=play_data) + self.send_msg(msg_type=MEDIA_TYPE_MAP["AUDIO"], sender=sender, message=message) + + def register_event(self): + super().register_event() + self.signal.connect(self._play, sender="anonymous") + + +if __name__ == '__main__': + media = MediaService() + media.start() + media.audio.setVolume(2) + media.tts_play(play_data="123") diff --git a/code/usr/bin/net_service.py b/code/usr/bin/net_service.py new file mode 100644 index 0000000..96a49b3 --- /dev/null +++ b/code/usr/bin/net_service.py @@ -0,0 +1,143 @@ +from usr.bin.components.abstract_service import AbstractService +from usr.utils.service_utils import Singleton +from usr.bin.components.monitor import ServiceMonitor +import dataCall +import net +import sim +import checkNet + +__all__ = ["NetService"] +NET = "NET" + +ENABLE_ = "enable" +NET_ = "NET" +DATACALL_ = "DATACALL" + + +class NetServiceMonitor(ServiceMonitor): + + @staticmethod + def create_monitor(config=None): + net_service = NetService() + nsm = NetServiceMonitor(net_service) + if config is not None: + nsm.set_exception_handlers(config.get('exceptionHandlers', None)) + return nsm + + +@Singleton +class NetService(AbstractService): + def __init__(self): + super().__init__(NET) + + self.__data_call = dataCall + + self.__net = net + + self.__sim = sim + + self.__net_connect_status = False + + self.__data_call_status = False + + self.server_status = { + DATACALL_: {ENABLE_: 1}, + NET_: {ENABLE_: 0} + } + self.check_net = checkNet.CheckNetwork("QuecPython_Helios_Framework", "this latest version") + + @property + def sim(self): + return self.__sim + + @property + def data_call(self): + return self.__data_call + + @property + def net(self): + return self.__net + + def set_enable(self, sr, enable): + """set start""" + self.server_status[sr][ENABLE_] = enable + + def set_apn(self, profileIdx, ipType, apn, username, password, authType): + return self.__data_call.setApn(profileIdx, ipType, apn, username, password, authType) + + def ev_dc(self, args): + if self.server_status[DATACALL_][ENABLE_]: + profile_idx = args[0] + datacall_status = args[1] + + msg_dict = \ + { + "sim_status": None, + "net_status": None, + "datacall_status": datacall_status, + "profile_id": profile_idx, + "ip_type": None, + "IPv4": None, + "IPv4_DNS1": None, + "IPv4_DNS2": None, + "IPv6": None, + "IPv6_DNS1": None, + "IPv6_DNS2": None, + } + + sim_status = self.__sim.getStatus() + net_status = self.__net.getState() + datacall_info = self.__data_call.getInfo(profile_idx, 2) + + msg_dict.update({"sim_status": sim_status}) + + if net_status != -1: + if net_status[0][0] == 0 or net_status[1][0] == 0: + msg_dict.update({"net_status": 0}) + else: + msg_dict.update({"net_status": net_status[1][0]}) + else: + msg_dict.update({"net_status": -1}) + if datacall_info != -1: + if datacall_info[2][0] == 1 or datacall_info[3][0] == 1: + msg_dict.update({"datacall_status": 1}) + else: + msg_dict.update({"datacall_status": 0}) + msg_dict.update({"ip_type": datacall_info[1]}) + msg_dict.update({"IPv4": datacall_info[2][2]}) + msg_dict.update({"IPv4_DNS1": datacall_info[2][3]}) + msg_dict.update({"IPv4_DNS2": datacall_info[2][4]}) + msg_dict.update({"IPv6": datacall_info[3][2]}) + msg_dict.update({"IPv6_DNS1": datacall_info[3][3]}) + msg_dict.update({"IPv6_DNS2": datacall_info[3][4]}) + self.send_msg(msg_type=1, message=msg_dict) + + def ev_nc(self, args): + if self.server_status[NET_][ENABLE_]: + net_status = args[1] + self.send_msg(msg_type=0, message={"network_register_status": net_status}) + + def wait_connect(self, timeout): + self.check_net.poweron_print_once() + return self.check_net.wait_network_connected(timeout) + + def get_net_status(self): + self.__data_call_status = self.__data_call.getInfo(1, 0)[2][0] + return self.__data_call_status + + def register_event(self): + self.__data_call.setCallback(self.ev_dc) + # self.__net.setCallback(self.ev_nc) + + +if __name__ == '__main__': + net_ser = NetService() + + + @net_ser.signal.connect_via(0X01) + def test(*args, **kwargs): + print(kwargs) + + + net_ser.start() + net_ser.send(sender=1, message={}) diff --git a/code/usr/bin/pm_service.py b/code/usr/bin/pm_service.py new file mode 100644 index 0000000..e01a9af --- /dev/null +++ b/code/usr/bin/pm_service.py @@ -0,0 +1,52 @@ +import pm +from usr.bin.components.abstract_service import AbstractService +from usr.utils.service_utils import Singleton +from usr.bin.components.monitor import ServiceMonitor + +ENABLE_ = "enable" +NET_ = "NET" +DATACALL_ = "DATACALL" + + +class PMServiceMonitor(ServiceMonitor): + + @staticmethod + def create_monitor(config=None): + ps_service = PMService() + psm = PMServiceMonitor(ps_service) + if config is not None: + psm.set_exception_handlers(config.get('exceptionHandlers', None)) + return psm + + +PM = "pm" + + +@Singleton +class PMService(AbstractService): + def __init__(self, flag=1): + super().__init__(PM) + self.pm = pm + self.__pm_lock = pm.create_wakelock("pm_lock", len("pm_lock")) + self.__flag = flag + self.__count = 0 + + def lock(self): + self.__count += 1 + self.pm.wakelock_lock(self.__pm_lock) + + def unlock(self): + self.__count -= 1 + if self.count() < 1: + self.pm.wakelock_unlock(self.__pm_lock) + + def count(self): + if self.__count < 0: + self.__count = 0 + return self.__count + + def auto_sleep(self, flag): + self.pm.autosleep(flag) + + def register_event(self): + self.pm.autosleep(1) diff --git a/code/usr/bin/third_party/offline_storage.py b/code/usr/bin/third_party/offline_storage.py new file mode 100644 index 0000000..c1b71cb --- /dev/null +++ b/code/usr/bin/third_party/offline_storage.py @@ -0,0 +1,202 @@ +try: + import uos as os + import utime as time + import ujson as json +except: + import os + import time + import json + + +def Singleton(cls): + _instance = {} + + def _singleton(*args, **kargs): + if cls not in _instance: + _instance[cls] = cls(*args, **kargs) + return _instance[cls] + + return _singleton + + +@Singleton +class OfflineStorage: + + def __init__(self): + self.status = None + self.split_file = False + self.single_file_max = 100 + self._file_name_no = 1 + self._rec_count = 1 + # self.default_dir = "/usr/offline_storage/" + self.default_dir = "./" + self._check_dir() + + def _check_dir(self): + try: + os.chdir(self.default_dir) + except: + raise SystemError("Directory is not exist!") + + @staticmethod + def _msg_no_gen(): + # ts = time.time() + ts = int(time.time()) + ts_str = str(ts) + if len(ts_str) > 5: + ts = int(ts_str[-5:]) + # 避免时间戳生成时间太短导致重复 + time.sleep(1) + return ts + + def _get_file_list(self): + file_list = os.listdir(self.default_dir) + file_list.sort() + # 筛选后缀名 + file_filtered = [] + for file in file_list: + try: + if file[-5:] == ".json": + file_filtered.append(file) + except: + continue + return file_filtered + + def _pre_load(self): + # 预读取 + data_map = dict() + file_list = self._get_file_list() + if not file_list: + return data_map + file = file_list.pop() + with open(self.default_dir + file, 'r', encoding="utf-8") as f: + try: + file_map = json.load(f) + except: + file_map = dict() + self._rec_count = len(file_map.keys()) + data_map.update(file_map) + return data_map + + def _write_file(self, data): + file_list = self._get_file_list() + file_list.sort() + if not file_list: + if self.split_file: + if self._rec_count > self.single_file_max: + self._file_name_no += 1 + file_name = "data%d.json" % self._file_name_no + else: + file_name = "data.json" + else: + file_name = file_list.pop() + with open(self.default_dir+file_name, "w+", encoding="utf-8") as f: + json.dump(data, f) + self._rec_count += 1 + + def deposit(self, data): + if self.status == 'r': + return False + self.status = 'w' + # 序号生成 + index = self._msg_no_gen() + format_data = {index: data} + data_map = self._pre_load() + data_map.update(format_data) + self._write_file(data_map) + self.status = None + return index + + def take_out(self): + if self.status == 'w': + return False + self.status = 'r' + file_list = self._get_file_list() + if not file_list: + return False + file_list.sort() + data_map = dict() + for file in file_list: + with open(self.default_dir + file, 'r', encoding="utf-8") as f: + try: + data_map.update(json.load(f)) + except: + pass + os.remove(self.default_dir + file) + self.status = None + return data_map + + def take_out_iter(self): + data_map = self.take_out() + for k, v in data_map.items(): + yield k, v + + def take_out_list(self): + data_map = self.take_out() + return list(data_map.values()) + + def take_out_by_index(self, index): + if self.status == 'w': + return False + self.status = 'r' + file_list = self._get_file_list() + if not file_list: + return None + file_list.sort() + for file in file_list: + with open(self.default_dir + file, 'r+', encoding="utf-8") as f: + file_map = json.load(f) + if index in file_map: + data = file_map.pop(index) + json.dump(file_map, f) + return data + return None + + def take_out_last(self, count=1): + if self.status == 'w': + return False + self.status = 'r' + file_list = self._get_file_list() + if not file_list: + return None + file_list.sort(reverse=True) + take_out_count = 0 + data_map = dict() + for file in file_list: + with open(self.default_dir + file, 'r+', encoding="utf-8") as f: + file_map = json.load(f) + items = sorted(file_map.items()) + while take_out_count <= count and items: + key, values = items.pop() + data_map[key] = values + file_map.pop(key) + json.dump(file_map, f) + if not items: + os.remove(self.default_dir + file) + return data_map + + def count(self): + file_list = self._get_file_list() + data_count = 0 + for file in file_list: + with open(self.default_dir + file, 'r', encoding="utf-8") as f: + try: + data_count += len(json.load(f)) + except: + pass + return data_count + + def preview_data(self): + data_map = dict() + file_list = self._get_file_list() + if not file_list: + return data_map + file_list.sort() + for file in file_list: + with open(self.default_dir + file, 'r', encoding="utf-8") as f: + try: + data_map.update(json.load(f)) + except: + pass + return data_map + diff --git a/code/usr/bin/third_party/ql_interrupter.py b/code/usr/bin/third_party/ql_interrupter.py new file mode 100644 index 0000000..553dac9 --- /dev/null +++ b/code/usr/bin/third_party/ql_interrupter.py @@ -0,0 +1,189 @@ +import osTimer, sys_bus, utime +from machine import Pin, ExtInt +import _thread + +""" +ExInt阻断器 + enable + disable +""" + + +class ExIntInterrupter(object): + def __init__(self, gpio, trige_mode=ExtInt.IRQ_RISING, pull_mode=ExtInt.PULL_DISABLE, callback=None): + self.__ext = ExtInt(gpio, trige_mode, pull_mode, callback) + + def enable(self): + return self.__ext.enable() + + def disable(self): + return self.__ext.disable() + + def start(self): + return self.enable() + + def stop(self): + return self.disable() + + +""" +Pin阻断器 + write + read +""" + + +class PinInterrupter(object): + + def __init__(self, gpio, trige_mode=Pin.OUT, pull_mode=Pin.PULL_DISABLE, mode=0): + self.gpio = Pin(gpio, trige_mode, pull_mode, mode) + + def write(self, value): + self.gpio.write(value) + + def read(self): + return self.gpio.read() + + def blinker(self, keep_time): + self.write(1) + utime.sleep(keep_time) + self.write(0) + + def __blinker(self, count, keep_time): + while count > 0: + self.blinker(keep_time) + + def freq_blinker(self, freq, keep_time): + return _thread.start_new_thread(self.blinker, (freq, keep_time)) + + +""" +具有BUS功能的ExInt阻断器 + publish +""" + + +class BusInterrupter(object): + + def register_callback(self, callback): + if callback is not None: + sys_bus.subscribe(self.topic, callback) + + def default_callback(self, *args, **kwargs): + self.publish(*args, **kwargs) + + def publish(self, *args, **kwargs): + sys_bus.publish(self.topic, kwargs) + + +class BusExIntInterrupter(ExIntInterrupter, BusInterrupter): + def __init__(self, topic, gpio, trige_mode=ExtInt.IRQ_RISING, pull_mode=ExtInt.PULL_DISABLE): + self.topic = topic + super().__init__(gpio, trige_mode, pull_mode, self.default_callback) + + def default_callback(self, *args, **kwargs): + self.publish(**dict(gpio=args[0][0], pressure=args[0][1])) + + +""" +定时器阻断器 + start + stop +""" + + +class TimerInterrupter(object): + def __init__(self, keep_time, callback, period=1): + """ + + @param period: 周期 + @param callback: 回调 + @param loop: 1 循环 0 是一次 + """ + self.__timer = osTimer() + self.__keep_time = keep_time + self.__callback = callback + self.__period = period + + def start(self): + self.__timer.start(self.__keep_time, self.__period, self.__callback) + + def stop(self): + self.__timer.stop() + + +""" +看门狗 + 继承于BusExInt 具有bus和ExInt的能力 + 组合了 + 定时器阻断器 + Pin阻断器 +""" + + +class WatchDog(object): + TOPIC = "WDT_KICK_TOPIC" + + def __init__(self, gpio, mode, keep_time, done_pin=None, trige_mode=ExtInt.IRQ_RISING, + pull_mode=ExtInt.PULL_DISABLE): + self.timer_inter = TimerInterrupter(keep_time, self.__process) + self.pin_inter = PinInterrupter(gpio, mode=mode) + if done_pin is not None: + self.bus_ext = BusExIntInterrupter(self.TOPIC, done_pin, trige_mode=trige_mode, pull_mode=pull_mode) + self.bus_ext.enable() + + def start(self): + self.timer_inter.start() + + def stop(self): + self.timer_inter.stop() + + def __process(self, *args, **kwargs): + self.pin_inter.blinker(1) + kwargs.update(dict(msg=self.TOPIC + "_FEED")) + sys_bus.publish(self.TOPIC + "_FEED", kwargs) + + +""" +ExInt处理器 + 继承于BusExInt 具有bus和ExInt的能力 +""" + + +class ExIntProcess(BusExIntInterrupter): + TOPIC = "GPIO{}_EXINT" + + def __init__(self, pin, trige_mode=Pin.OUT, pull_mode=Pin.PULL_DISABLE): + super().__init__(self.TOPIC.format(pin), pin, trige_mode, pull_mode) + + +if __name__ == '__main__': + def pin_interrupt_callback(topic, message): + print("pin_interrupt_callback, topic: {}, message: {}".format(topic, message)) + + + def ext_interrupt_callback(topic, message): + print("ext_interrupt_callback, topic: {}, message: {}".format(topic, message)) + + + def kick_feed_interrupt_callback(topic, message): + print("kick_feed_interrupt_callback, topic: {}, message: {}".format(topic, message)) + + + # 订阅gpio的中断 + sys_bus.subscribe("GPIO18_EXINT", pin_interrupt_callback) + + # 订阅WDT_KICK_TOPIC订阅喂狗中断的回调 + sys_bus.subscribe("WDT_KICK_TOPIC", ext_interrupt_callback) + + # 订阅喂狗的回调, 每次喂狗都会触发次订阅的回调 + sys_bus.subscribe("WDT_KICK_TOPIC_FEED", kick_feed_interrupt_callback) + # 初始化狗 + wd = WatchDog(Pin.GPIO15, 1, 20000, Pin.GPIO8) + # 开启喂狗 + wd.start() + + # 初始化中断的处理器 + ep = ExIntProcess(Pin.GPIO5) + # 开启中断 + ep.start() diff --git a/code/usr/utils/JsonParserUtils.py b/code/usr/utils/JsonParserUtils.py new file mode 100644 index 0000000..1868749 --- /dev/null +++ b/code/usr/utils/JsonParserUtils.py @@ -0,0 +1,33 @@ +import uos +import ujson + + +class Parse(object): + def parse(self, *args, **kwargs): + """parse interface""" + + +class JsonParser(object): + DEFAULT_FILE_NAME = "config.json" + + @classmethod + def composite_url(cls, url): + if not url.endswith("/"): + url += "/" + return url + cls.DEFAULT_FILE_NAME + + @classmethod + def parse(cls, url): + rep_d = dict( + status=1, + data=dict() + ) + try: + url = cls.composite_url(url) + with open(url, "r") as f: + rep_d["data"] = ujson.load(f) + except Exception as e: + rep_d["status"] = 0 + return rep_d + else: + return rep_d diff --git a/code/usr/utils/resolver.py b/code/usr/utils/resolver.py new file mode 100644 index 0000000..fa62f05 --- /dev/null +++ b/code/usr/utils/resolver.py @@ -0,0 +1,21 @@ +import utime + + +class TimeResolver(object): + + def __init__(self): + self.output_format = "{ascdate} {asctime} {ascweek}" + self.weekday_list = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + + def resolver(self, rt=None): + if rt is None: + rt = utime.localtime() + d_f = "{0:02}" + return self.output_format.format( + ascdate=str(rt[0]) + "-" + d_f.format(rt[1]) + "-" + d_f.format(rt[2]) + , asctime=d_f.format(rt[3]) + ":" + d_f.format(rt[4]) + ":" + d_f.format(rt[5]), + ascweek=self.weekday_list[rt[6]]) + + +if __name__ == '__main__': + print(TimeResolver().resolver()) diff --git a/code/usr/utils/service_utils.py b/code/usr/utils/service_utils.py new file mode 100644 index 0000000..efcb55b --- /dev/null +++ b/code/usr/utils/service_utils.py @@ -0,0 +1,9 @@ +def Singleton(cls): + _instance = {} + + def _singleton(*args, **kargs): + if cls not in _instance: + _instance[cls] = cls(*args, **kargs) + return _instance[cls] + + return _singleton diff --git a/helios service文档/Helio_Service指南(1)_入门.pdf b/helios service文档/Helio_Service指南(1)_入门.pdf new file mode 100644 index 0000000..9b7f3d7 Binary files /dev/null and b/helios service文档/Helio_Service指南(1)_入门.pdf differ diff --git a/helios service文档/Helio_Service指南(2)_进阶.pdf b/helios service文档/Helio_Service指南(2)_进阶.pdf new file mode 100644 index 0000000..a8f8eef Binary files /dev/null and b/helios service文档/Helio_Service指南(2)_进阶.pdf differ diff --git a/helios service文档/Helio_Service指南(3)_高级.pdf b/helios service文档/Helio_Service指南(3)_高级.pdf new file mode 100644 index 0000000..ae29e49 Binary files /dev/null and b/helios service文档/Helio_Service指南(3)_高级.pdf differ