2022-03-24 16:01:12 +08:00
|
|
|
# Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
2022-04-05 14:56:50 +08:00
|
|
|
|
2022-03-26 19:09:18 +08:00
|
|
|
import utime
|
2022-03-17 16:44:47 +08:00
|
|
|
import osTimer
|
2022-03-03 09:53:51 +08:00
|
|
|
import quecIot
|
2022-03-18 17:53:46 +08:00
|
|
|
|
2022-03-17 16:44:47 +08:00
|
|
|
from queue import Queue
|
2022-03-18 17:53:46 +08:00
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
from usr.ota import SOTA
|
2022-03-03 09:53:51 +08:00
|
|
|
from usr.logging import getLogger
|
2022-04-13 11:55:36 +08:00
|
|
|
from usr.common import CloudObservable, CloudObjectModel
|
2022-03-03 09:53:51 +08:00
|
|
|
|
2022-03-04 13:21:48 +08:00
|
|
|
log = getLogger(__name__)
|
|
|
|
|
2022-03-04 19:29:23 +08:00
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
EVENT_CODE = {
|
|
|
|
1: {
|
|
|
|
10200: "Device authentication succeeded.",
|
|
|
|
10420: "Bad request data (connection failed).",
|
|
|
|
10422: "Device authenticated (connection failed).",
|
|
|
|
10423: "No product information found (connection failed).",
|
|
|
|
10424: "PAYLOAD parsing failed (connection failed).",
|
|
|
|
10425: "Signature verification failed (connection failed).",
|
|
|
|
10426: "Bad authentication version (connection failed).",
|
|
|
|
10427: "Invalid hash information (connection failed).",
|
|
|
|
10430: "PK changed (connection failed).",
|
|
|
|
10431: "Invalid DK (connection failed).",
|
|
|
|
10432: "PK does not match authentication version (connection failed).",
|
|
|
|
10450: "Device internal error (connection failed).",
|
|
|
|
10466: "Boot server address not found (connection failed).",
|
|
|
|
10500: "Device authentication failed (an unknown exception occurred in the system).",
|
|
|
|
10300: "Other errors.",
|
|
|
|
},
|
|
|
|
2: {
|
|
|
|
10200: "Access is successful.",
|
|
|
|
10430: "Incorrect device key (connection failed).",
|
|
|
|
10431: "Device is disabled (connection failed).",
|
|
|
|
10450: "Device internal error (connection failed).",
|
|
|
|
10471: "Implementation version not supported (connection failed).",
|
|
|
|
10473: "Abnormal access heartbeat (connection timed out).",
|
|
|
|
10474: "Network exception (connection timed out).",
|
|
|
|
10475: "Server changes.",
|
|
|
|
10476: "Abnormal connection to AP.",
|
|
|
|
10500: "Access failed (an unknown exception occurred in the system).",
|
|
|
|
},
|
|
|
|
3: {
|
|
|
|
10200: "Subscription succeeded.",
|
|
|
|
10300: "Subscription failed.",
|
|
|
|
},
|
|
|
|
4: {
|
|
|
|
10200: "Transparent data sent successfully.",
|
|
|
|
10210: "Object model data sent successfully.",
|
|
|
|
10220: "Positioning data sent successfully.",
|
|
|
|
10300: "Failed to send transparent data.",
|
|
|
|
10310: "Failed to send object model data.",
|
|
|
|
10320: "Failed to send positioning data.",
|
|
|
|
},
|
|
|
|
5: {
|
|
|
|
10200: "Receive transparent data.",
|
|
|
|
10210: "Receive data from the object model.",
|
|
|
|
10211: "Received object model query command.",
|
|
|
|
10473: "Received data but the length exceeds the module buffer limit, receive failed.",
|
|
|
|
10428: "The device receives too much buffer and causes current limit.",
|
|
|
|
},
|
|
|
|
6: {
|
|
|
|
10200: "Logout succeeded (disconnection succeeded).",
|
|
|
|
},
|
|
|
|
7: {
|
|
|
|
10700: "New OTA plain.",
|
|
|
|
10701: "The module starts to download.",
|
|
|
|
10702: "Package download.",
|
|
|
|
10703: "Package download complete.",
|
|
|
|
10704: "Package update.",
|
|
|
|
10705: "Firmware update complete.",
|
|
|
|
10706: "Failed to update firmware.",
|
|
|
|
10707: "Received confirmation broadcast.",
|
|
|
|
},
|
|
|
|
8: {
|
|
|
|
10428: "High-frequency messages on the device cause current throttling.",
|
|
|
|
10429: "Exceeds the number of activations per device or daily requests current limit.",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-04-13 11:55:36 +08:00
|
|
|
class QuecObjectModel(CloudObjectModel):
|
2022-04-12 20:16:58 +08:00
|
|
|
|
|
|
|
def __init__(self):
|
2022-04-13 11:55:36 +08:00
|
|
|
super().__init__()
|
2022-04-12 20:16:58 +08:00
|
|
|
self.items_id = {}
|
|
|
|
|
2022-04-13 11:55:36 +08:00
|
|
|
def __init_items_id(self, om_key, om_key_id):
|
|
|
|
self.items_id[om_key_id] = om_key
|
|
|
|
return True
|
2022-04-12 20:16:58 +08:00
|
|
|
|
2022-04-13 11:55:36 +08:00
|
|
|
def __del_items_id(self, om_type, om_key):
|
|
|
|
if self.items.get(om_type) is not None:
|
|
|
|
if self.items[om_type].get(om_key):
|
2022-04-13 18:24:49 +08:00
|
|
|
om_key_id = self.items[om_type][om_key]["id"]
|
2022-04-13 11:55:36 +08:00
|
|
|
self.items_id.pop(om_key_id)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def set_item(self, om_type, om_key, om_key_id, om_key_perm):
|
|
|
|
if super().set_item(om_type, om_key, om_key_id=om_key_id, om_key_perm=om_key_perm):
|
|
|
|
self.__init_items_id(om_key, om_key_id)
|
2022-04-12 20:16:58 +08:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2022-04-13 11:55:36 +08:00
|
|
|
def del_item(self, om_type, om_key):
|
|
|
|
self.__del_items_id(om_type, om_key)
|
|
|
|
return super().del_item(om_type, om_key)
|
2022-04-12 20:16:58 +08:00
|
|
|
|
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
class QuecThing(CloudObservable):
|
|
|
|
def __init__(self, pk, ps, dk, ds, server, life_time=120, mcu_name="", mcu_version=""):
|
|
|
|
super().__init__()
|
2022-04-12 20:16:58 +08:00
|
|
|
self.__pk = pk
|
|
|
|
self.__ps = ps
|
|
|
|
self.__dk = dk
|
|
|
|
self.__ds = ds
|
|
|
|
self.__server = server
|
|
|
|
self.__life_time = life_time
|
|
|
|
self.__mcu_name = mcu_name
|
|
|
|
self.__mcu_version = mcu_version
|
2022-04-13 11:55:36 +08:00
|
|
|
self.__object_model = None
|
2022-04-12 20:16:58 +08:00
|
|
|
|
|
|
|
self.__file_size = 0
|
|
|
|
self.__md5_value = ""
|
|
|
|
self.__post_result_wait_queue = Queue(maxsize=16)
|
|
|
|
self.__quec_timer = osTimer()
|
2022-04-12 09:13:20 +08:00
|
|
|
|
|
|
|
def __rm_empty_data(self, data):
|
|
|
|
for k, v in data.items():
|
|
|
|
if not v:
|
|
|
|
del data[k]
|
|
|
|
|
|
|
|
def __quec_timer_cb(self, args):
|
|
|
|
self.__put_post_res(False)
|
|
|
|
|
|
|
|
def __get_post_res(self):
|
2022-04-13 11:55:36 +08:00
|
|
|
self.__quec_timer.start(1000 * 10, 0, self.__quec_timer_cb)
|
2022-04-12 20:16:58 +08:00
|
|
|
res = self.__post_result_wait_queue.get()
|
|
|
|
self.__quec_timer.stop()
|
2022-04-12 09:13:20 +08:00
|
|
|
return res
|
|
|
|
|
|
|
|
def __put_post_res(self, res):
|
2022-04-12 20:16:58 +08:00
|
|
|
if self.__post_result_wait_queue.size() >= 16:
|
|
|
|
self.__post_result_wait_queue.get()
|
|
|
|
self.__post_result_wait_queue.put(res)
|
2022-04-12 09:13:20 +08:00
|
|
|
|
|
|
|
def __sota_download_info(self, size, md5_value):
|
2022-04-12 20:16:58 +08:00
|
|
|
self.__file_size = size
|
|
|
|
self.__md5_value = md5_value
|
2022-04-12 09:13:20 +08:00
|
|
|
|
|
|
|
def __sota_upgrade_start(self, start_addr, need_download_size):
|
|
|
|
download_size = 0
|
|
|
|
sota_mode = SOTA()
|
|
|
|
while need_download_size != 0:
|
|
|
|
readsize = 4096
|
|
|
|
if (readsize > need_download_size):
|
|
|
|
readsize = need_download_size
|
|
|
|
updateFile = quecIot.mcuFWDataRead(start_addr, readsize)
|
|
|
|
sota_mode.write_update_data(updateFile)
|
|
|
|
log.debug("Download File Size: %s" % readsize)
|
|
|
|
need_download_size -= readsize
|
|
|
|
start_addr += readsize
|
|
|
|
download_size += readsize
|
2022-04-12 20:16:58 +08:00
|
|
|
if (download_size == self.__file_size):
|
2022-04-12 09:13:20 +08:00
|
|
|
log.debug("File Download Success, Update Start.")
|
|
|
|
self.ota_action(3)
|
2022-04-12 20:16:58 +08:00
|
|
|
if sota_mode.check_md5(self.__md5_value):
|
2022-04-12 09:13:20 +08:00
|
|
|
if sota_mode.file_update():
|
|
|
|
sota_mode.sota_set_flag()
|
|
|
|
log.debug("File Update Success, Power Restart.")
|
|
|
|
else:
|
|
|
|
log.debug("File Update Failed, Power Restart.")
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
self.ota_action(2)
|
|
|
|
|
|
|
|
res_data = ("object_model", [("power_restart", 1)])
|
|
|
|
self.notifyObservers(self, *res_data)
|
|
|
|
|
2022-04-13 11:55:36 +08:00
|
|
|
def __data_format(self, k, v):
|
|
|
|
# log.debug("k: %s, v: %s" % (k, v))
|
|
|
|
k_id = None
|
|
|
|
struct_info = {}
|
2022-04-13 18:24:49 +08:00
|
|
|
if self.__object_model.items["event"].get(k):
|
|
|
|
k_id = self.__object_model.items["event"][k]["id"]
|
|
|
|
if isinstance(self.__object_model.items["event"][k]["struct_info"], dict):
|
|
|
|
struct_info = self.__object_model.items["event"][k]["struct_info"]
|
|
|
|
elif self.__object_model.items["property"].get(k):
|
|
|
|
k_id = self.__object_model.items["property"][k]["id"]
|
|
|
|
if isinstance(self.__object_model.items["property"][k]["struct_info"], dict):
|
|
|
|
struct_info = self.__object_model.items["property"][k]["struct_info"]
|
2022-04-13 11:55:36 +08:00
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if isinstance(v, dict):
|
|
|
|
nv = {}
|
|
|
|
for ik, iv in v.items():
|
|
|
|
if struct_info.get(ik):
|
|
|
|
nv[struct_info[ik]["id"]] = iv
|
|
|
|
else:
|
|
|
|
nv[ik] = iv
|
|
|
|
v = nv
|
|
|
|
|
|
|
|
return {k_id: v}
|
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
def __event_cb(self, data):
|
|
|
|
res_data = ()
|
|
|
|
event = data[0]
|
|
|
|
errcode = data[1]
|
|
|
|
eventdata = b""
|
|
|
|
if len(data) > 2:
|
|
|
|
eventdata = data[2]
|
|
|
|
log.info("Event[%s] ErrCode[%s] Msg[%s] EventData[%s]" % (event, errcode, EVENT_CODE.get(event, {}).get(errcode, ""), eventdata))
|
|
|
|
|
|
|
|
if event == 3:
|
|
|
|
if errcode == 10200:
|
|
|
|
if eventdata:
|
|
|
|
file_info = eval(eventdata)
|
|
|
|
log.info("OTA File Info: componentNo: %s, sourceVersion: %s, targetVersion: %s, "
|
|
|
|
"batteryLimit: %s, minSignalIntensity: %s, minSignalIntensity: %s" % file_info)
|
|
|
|
elif event == 4:
|
|
|
|
if errcode == 10200:
|
|
|
|
self.__put_post_res(True)
|
|
|
|
elif errcode == 10210:
|
|
|
|
self.__put_post_res(True)
|
|
|
|
elif errcode == 10220:
|
|
|
|
self.__put_post_res(True)
|
|
|
|
elif errcode == 10300:
|
|
|
|
self.__put_post_res(False)
|
|
|
|
elif errcode == 10310:
|
|
|
|
self.__put_post_res(False)
|
|
|
|
elif errcode == 10320:
|
|
|
|
self.__put_post_res(False)
|
|
|
|
elif event == 5:
|
|
|
|
if errcode == 10200:
|
|
|
|
# TODO: Data Type Passthrough (Not Support Now).
|
|
|
|
res_data = ("raw_data", eventdata)
|
|
|
|
elif errcode == 10210:
|
2022-04-12 20:16:58 +08:00
|
|
|
dl_data = [(self.__object_model.items_id[k], v.decode() if isinstance(v, bytes) else v) for k, v in eventdata.items()]
|
2022-04-12 09:13:20 +08:00
|
|
|
res_data = ("object_model", dl_data)
|
|
|
|
elif errcode == 10211:
|
|
|
|
# eventdata[0] is pkgId.
|
|
|
|
object_model_ids = eventdata[1]
|
2022-04-12 20:16:58 +08:00
|
|
|
object_model_val = [self.__object_model.items_id[i] for i in object_model_ids if self.__object_model.items_id.get(i)]
|
2022-04-12 09:13:20 +08:00
|
|
|
res_data = ("query", object_model_val)
|
|
|
|
pass
|
|
|
|
elif event == 7:
|
|
|
|
if errcode == 10700:
|
|
|
|
if eventdata:
|
|
|
|
file_info = eval(eventdata)
|
|
|
|
log.info("OTA File Info: componentNo: %s, sourceVersion: %s, targetVersion: %s, "
|
|
|
|
"batteryLimit: %s, minSignalIntensity: %s, useSpace: %s" % file_info)
|
|
|
|
res_data = ("object_model", [("ota_status", (file_info[0], 1, file_info[2]))])
|
|
|
|
elif errcode == 10701:
|
|
|
|
res_data = ("object_model", [("ota_status", (None, 2, None))])
|
|
|
|
elif errcode == 10702:
|
|
|
|
res_data = ("object_model", [("ota_status", (None, 2, None))])
|
|
|
|
elif errcode == 10703:
|
|
|
|
res_data = ("object_model", [("ota_status", (None, 2, None))])
|
|
|
|
elif errcode == 10704:
|
|
|
|
res_data = ("object_model", [("ota_status", (None, 2, None))])
|
|
|
|
elif errcode == 10705:
|
|
|
|
res_data = ("object_model", [("ota_status", (None, 3, None))])
|
|
|
|
elif errcode == 10706:
|
|
|
|
res_data = ("object_model", [("ota_status", (None, 4, None))])
|
|
|
|
|
|
|
|
if res_data:
|
|
|
|
self.notifyObservers(self, *res_data)
|
|
|
|
|
|
|
|
if event == 7 and errcode == 10701 and eventdata:
|
|
|
|
file_info = eval(eventdata)
|
|
|
|
self.__sota_download_info(int(file_info[1]), file_info[2])
|
|
|
|
if event == 7 and errcode == 10703 and eventdata:
|
|
|
|
file_info = eval(eventdata)
|
|
|
|
log.info("OTA File Info: componentNo: %s, length: %s, md5: %s, crc: %s" % file_info)
|
|
|
|
self.__sota_upgrade_start(int(file_info[2]), int(file_info[3]))
|
2022-03-31 13:41:08 +08:00
|
|
|
|
2022-04-12 20:16:58 +08:00
|
|
|
def set_object_model(self, object_model):
|
2022-04-13 11:55:36 +08:00
|
|
|
if object_model and isinstance(object_model, QuecObjectModel):
|
2022-04-12 20:16:58 +08:00
|
|
|
self.__object_model = object_model
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def init(self, enforce=False):
|
2022-04-05 12:09:23 +08:00
|
|
|
log.debug(
|
2022-04-12 20:16:58 +08:00
|
|
|
"[init start] enforce: %s QuecThing Work State: %s, quecIot.getConnmode(): %s"
|
2022-04-05 12:09:23 +08:00
|
|
|
% (enforce, quecIot.getWorkState(), quecIot.getConnmode())
|
|
|
|
)
|
2022-04-13 18:24:49 +08:00
|
|
|
log.debug("[init start] PK: %s, PS: %s, DK: %s, DS: %s, SERVER: %s" % (self.__pk, self.__ps, self.__dk, self.__ds, self.__server))
|
2022-04-05 12:09:23 +08:00
|
|
|
if enforce is False:
|
|
|
|
if quecIot.getWorkState() == 8 and quecIot.getConnmode() == 1:
|
|
|
|
return True
|
2022-03-16 11:38:50 +08:00
|
|
|
|
2022-03-17 13:25:59 +08:00
|
|
|
quecIot.init()
|
2022-04-12 09:13:20 +08:00
|
|
|
quecIot.setEventCB(self.__event_cb)
|
2022-04-12 20:16:58 +08:00
|
|
|
quecIot.setProductinfo(self.__pk, self.__ps)
|
|
|
|
if self.__dk or self.__ds:
|
|
|
|
quecIot.setDkDs(self.__dk, self.__ds)
|
|
|
|
quecIot.setServer(1, self.__server)
|
|
|
|
quecIot.setLifetime(self.__life_time)
|
|
|
|
quecIot.setMcuVersion(self.__mcu_name, self.__mcu_version)
|
2022-03-03 09:53:51 +08:00
|
|
|
quecIot.setConnmode(1)
|
2022-03-31 13:41:08 +08:00
|
|
|
|
|
|
|
count = 0
|
|
|
|
while quecIot.getWorkState() != 8 and count < 10:
|
|
|
|
utime.sleep_ms(200)
|
2022-04-05 12:09:23 +08:00
|
|
|
count += 1
|
2022-03-31 13:41:08 +08:00
|
|
|
|
2022-04-12 20:16:58 +08:00
|
|
|
if not self.__ds and self.__dk:
|
2022-03-26 19:09:18 +08:00
|
|
|
count = 0
|
|
|
|
while count < 3:
|
2022-04-05 12:09:23 +08:00
|
|
|
dkds = quecIot.getDkDs()
|
|
|
|
if dkds:
|
2022-04-12 20:16:58 +08:00
|
|
|
self.__dk, self.__ds = dkds
|
2022-04-12 09:13:20 +08:00
|
|
|
log.debug("dk: %s, ds: %s" % dkds)
|
|
|
|
res_data = (
|
|
|
|
"object_model",
|
2022-04-12 20:16:58 +08:00
|
|
|
[("init_params", {"PK": self.__pk, "PS": self.__ps, "DK": self.__dk, "DS": self.__ds, "SERVER": self.__server})]
|
2022-04-12 09:13:20 +08:00
|
|
|
)
|
|
|
|
self.notifyObservers(self, *res_data)
|
2022-03-26 19:09:18 +08:00
|
|
|
break
|
|
|
|
count += 1
|
|
|
|
utime.sleep(count)
|
2022-03-03 09:53:51 +08:00
|
|
|
|
2022-04-12 20:16:58 +08:00
|
|
|
log.debug("[init over] QuecThing Work State: %s, quecIot.getConnmode(): %s" % (quecIot.getWorkState(), quecIot.getConnmode()))
|
2022-04-05 12:09:23 +08:00
|
|
|
if quecIot.getWorkState() == 8 and quecIot.getConnmode() == 1:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2022-04-12 20:16:58 +08:00
|
|
|
def close(self):
|
2022-04-12 09:13:20 +08:00
|
|
|
return quecIot.setConnmode(0)
|
2022-04-06 11:33:16 +08:00
|
|
|
|
2022-03-23 14:21:45 +08:00
|
|
|
def post_data(self, data):
|
|
|
|
res = True
|
2022-04-12 09:13:20 +08:00
|
|
|
# log.debug("post_data: %s" % str(data))
|
2022-03-23 14:21:45 +08:00
|
|
|
for k, v in data.items():
|
2022-04-13 11:55:36 +08:00
|
|
|
om_data = self.__data_format(k, v)
|
|
|
|
if om_data is not False:
|
2022-03-28 20:29:01 +08:00
|
|
|
if v is not None:
|
2022-04-12 20:16:58 +08:00
|
|
|
phymodelReport_res = quecIot.phymodelReport(1, om_data)
|
2022-03-23 14:21:45 +08:00
|
|
|
if not phymodelReport_res:
|
|
|
|
res = False
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
continue
|
2022-04-12 09:13:20 +08:00
|
|
|
elif k == "gps":
|
2022-03-23 14:21:45 +08:00
|
|
|
locReportOutside_res = quecIot.locReportOutside(v)
|
|
|
|
if not locReportOutside_res:
|
|
|
|
res = False
|
|
|
|
break
|
2022-04-12 09:13:20 +08:00
|
|
|
elif k == "non_gps":
|
2022-03-23 14:21:45 +08:00
|
|
|
locReportInside_res = quecIot.locReportInside(v)
|
|
|
|
if not locReportInside_res:
|
|
|
|
res = False
|
|
|
|
break
|
2022-03-04 17:09:04 +08:00
|
|
|
else:
|
2022-03-23 14:21:45 +08:00
|
|
|
v = {}
|
|
|
|
continue
|
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
res = self.__get_post_res()
|
2022-03-23 14:21:45 +08:00
|
|
|
if res:
|
|
|
|
v = {}
|
2022-03-04 17:09:04 +08:00
|
|
|
else:
|
2022-03-23 14:21:45 +08:00
|
|
|
res = False
|
|
|
|
break
|
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
self.__rm_empty_data(data)
|
2022-03-23 14:21:45 +08:00
|
|
|
return res
|
2022-03-03 09:53:51 +08:00
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
def ota_request(self, mp_mode=0):
|
|
|
|
return quecIot.otaRequest(mp_mode) if mp_mode in (0, 1) else False
|
2022-03-31 13:41:08 +08:00
|
|
|
|
2022-04-12 09:13:20 +08:00
|
|
|
def ota_action(self, action=1, module=None):
|
|
|
|
return quecIot.otaAction(action) if action in (0, 1, 2, 3) else False
|