diff --git a/.gitignore b/.gitignore deleted file mode 100644 index c55d8d0..0000000 --- a/.gitignore +++ /dev/null @@ -1,113 +0,0 @@ -# Specify filepatterns you want git to ignore. - -.vscode - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version -.idea/ -# celery beat schedule file -celerybeat-schedule -workspace.xml -# SageMath parsed files -*.sage.py -.idea/workspace.xml -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -tracker_settings.json - -dev_settings_*.py -.test/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 76b45fa..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "code/modules"] - path = code/modules - url = https://github.com/QuecPython/modules.git diff --git a/BG93-M3-Tracker/LICENSE b/BG93-M3-Tracker/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/BG93-M3-Tracker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/BG93-M3-Tracker/README.md b/BG93-M3-Tracker/README.md new file mode 100644 index 0000000..13a04d7 --- /dev/null +++ b/BG93-M3-Tracker/README.md @@ -0,0 +1 @@ +# tracker_BG95-M3 \ No newline at end of file diff --git a/BG93-M3-Tracker/code/Qth/__init__.mpy b/BG93-M3-Tracker/code/Qth/__init__.mpy new file mode 100644 index 0000000..05fa4b8 Binary files /dev/null and b/BG93-M3-Tracker/code/Qth/__init__.mpy differ diff --git a/BG93-M3-Tracker/code/Qth/qth_bus.mpy b/BG93-M3-Tracker/code/Qth/qth_bus.mpy new file mode 100644 index 0000000..a9b8389 Binary files /dev/null and b/BG93-M3-Tracker/code/Qth/qth_bus.mpy differ diff --git a/BG93-M3-Tracker/code/Qth/qth_config.mpy b/BG93-M3-Tracker/code/Qth/qth_config.mpy new file mode 100644 index 0000000..88685f2 Binary files /dev/null and b/BG93-M3-Tracker/code/Qth/qth_config.mpy differ diff --git a/BG93-M3-Tracker/code/Qth/qth_dmp.mpy b/BG93-M3-Tracker/code/Qth/qth_dmp.mpy new file mode 100644 index 0000000..f2e9f6e Binary files /dev/null and b/BG93-M3-Tracker/code/Qth/qth_dmp.mpy differ diff --git a/BG93-M3-Tracker/code/Qth/qth_file.mpy b/BG93-M3-Tracker/code/Qth/qth_file.mpy new file mode 100644 index 0000000..dfc2f3c Binary files /dev/null and b/BG93-M3-Tracker/code/Qth/qth_file.mpy differ diff --git a/BG93-M3-Tracker/code/Qth/qth_ota.mpy b/BG93-M3-Tracker/code/Qth/qth_ota.mpy new file mode 100644 index 0000000..18eaa76 Binary files /dev/null and b/BG93-M3-Tracker/code/Qth/qth_ota.mpy differ diff --git a/BG93-M3-Tracker/code/Qth/qth_protocal.mpy b/BG93-M3-Tracker/code/Qth/qth_protocal.mpy new file mode 100644 index 0000000..ef8d78e Binary files /dev/null and b/BG93-M3-Tracker/code/Qth/qth_protocal.mpy differ diff --git a/BG93-M3-Tracker/code/config.json b/BG93-M3-Tracker/code/config.json new file mode 100644 index 0000000..3af9d81 --- /dev/null +++ b/BG93-M3-Tracker/code/config.json @@ -0,0 +1,5 @@ +{ + "QTH_PRODUCT_KEY": "pe16Db", + "QTH_PRODUCT_SECRET": "ZGZMQWQ3QkVyN2Jm", + "QTH_SERVER": "mqtt://iot-south.acceleronix.io:1883" +} \ No newline at end of file diff --git a/BG93-M3-Tracker/code/config2.json b/BG93-M3-Tracker/code/config2.json new file mode 100644 index 0000000..e5aa3b8 --- /dev/null +++ b/BG93-M3-Tracker/code/config2.json @@ -0,0 +1,5 @@ +{ + "QTH_PRODUCT_KEY": "pe17gQ", + "QTH_PRODUCT_SECRET": "REdmNmlMRS8yUmNi", + "QTH_SERVER": "mqtt://iot-south.quectelcn.com:1883" +} \ No newline at end of file diff --git a/BG93-M3-Tracker/code/drivers/icm20948.py b/BG93-M3-Tracker/code/drivers/icm20948.py new file mode 100644 index 0000000..cfbf9ae --- /dev/null +++ b/BG93-M3-Tracker/code/drivers/icm20948.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- +import time +import math +from usr.libs.i2c import I2CIOWrapper + +Gyro = [0,0,0] +Accel = [0,0,0] +Mag = [0,0,0] +pitch = 0.0 +roll = 0.0 +yaw = 0.0 +pu8data=[0,0,0,0,0,0,0,0] +U8tempX=[0,0,0,0,0,0,0,0,0] +U8tempY=[0,0,0,0,0,0,0,0,0] +U8tempZ=[0,0,0,0,0,0,0,0,0] +GyroOffset=[0,0,0] +Ki = 1.0 +Kp = 4.50 +q0 = 1.0 +q1=q2=q3=0.0 +angles=[0.0,0.0,0.0] +true =0x01 +false =0x00 +# define ICM-20948 Device I2C address +I2C_ADD_ICM20948 = 0x68 +I2C_ADD_ICM20948_AK09916 = 0x0C +I2C_ADD_ICM20948_AK09916_READ = 0x80 +I2C_ADD_ICM20948_AK09916_WRITE = 0x00 +# define ICM-20948 Register +# user bank 0 register +REG_ADD_WIA = 0x00 +REG_VAL_WIA = 0xEA +REG_ADD_USER_CTRL = 0x03 +REG_VAL_BIT_DMP_EN = 0x80 +REG_VAL_BIT_FIFO_EN = 0x40 +REG_VAL_BIT_I2C_MST_EN = 0x20 +REG_VAL_BIT_I2C_IF_DIS = 0x10 +REG_VAL_BIT_DMP_RST = 0x08 +REG_VAL_BIT_DIAMOND_DMP_RST = 0x04 +REG_ADD_PWR_MIGMT_1 = 0x06 +REG_VAL_ALL_RGE_RESET = 0x80 +REG_VAL_RUN_MODE = 0x01 # Non low-power mode +REG_ADD_LP_CONFIG = 0x05 +REG_ADD_PWR_MGMT_1 = 0x06 +REG_ADD_PWR_MGMT_2 = 0x07 +REG_ADD_ACCEL_XOUT_H = 0x2D +REG_ADD_ACCEL_XOUT_L = 0x2E +REG_ADD_ACCEL_YOUT_H = 0x2F +REG_ADD_ACCEL_YOUT_L = 0x30 +REG_ADD_ACCEL_ZOUT_H = 0x31 +REG_ADD_ACCEL_ZOUT_L = 0x32 +REG_ADD_GYRO_XOUT_H = 0x33 +REG_ADD_GYRO_XOUT_L = 0x34 +REG_ADD_GYRO_YOUT_H = 0x35 +REG_ADD_GYRO_YOUT_L = 0x36 +REG_ADD_GYRO_ZOUT_H = 0x37 +REG_ADD_GYRO_ZOUT_L = 0x38 +REG_ADD_EXT_SENS_DATA_00 = 0x3B +REG_ADD_REG_BANK_SEL = 0x7F +REG_VAL_REG_BANK_0 = 0x00 +REG_VAL_REG_BANK_1 = 0x10 +REG_VAL_REG_BANK_2 = 0x20 +REG_VAL_REG_BANK_3 = 0x30 + +# user bank 1 register +# user bank 2 register +REG_ADD_GYRO_SMPLRT_DIV = 0x00 +REG_ADD_GYRO_CONFIG_1 = 0x01 +REG_VAL_BIT_GYRO_DLPCFG_2 = 0x10 # bit[5:3] +REG_VAL_BIT_GYRO_DLPCFG_4 = 0x20 # bit[5:3] +REG_VAL_BIT_GYRO_DLPCFG_6 = 0x30 # bit[5:3] +REG_VAL_BIT_GYRO_FS_250DPS = 0x00 # bit[2:1] +REG_VAL_BIT_GYRO_FS_500DPS = 0x02 # bit[2:1] +REG_VAL_BIT_GYRO_FS_1000DPS = 0x04 # bit[2:1] +REG_VAL_BIT_GYRO_FS_2000DPS = 0x06 # bit[2:1] +REG_VAL_BIT_GYRO_DLPF = 0x01 # bit[0] +REG_ADD_ACCEL_SMPLRT_DIV_2 = 0x11 +REG_ADD_ACCEL_CONFIG = 0x14 +REG_VAL_BIT_ACCEL_DLPCFG_2 = 0x10 # bit[5:3] +REG_VAL_BIT_ACCEL_DLPCFG_4 = 0x20 # bit[5:3] +REG_VAL_BIT_ACCEL_DLPCFG_6 = 0x30 # bit[5:3] +REG_VAL_BIT_ACCEL_FS_2g = 0x00 # bit[2:1] +REG_VAL_BIT_ACCEL_FS_4g = 0x02 # bit[2:1] +REG_VAL_BIT_ACCEL_FS_8g = 0x04 # bit[2:1] +REG_VAL_BIT_ACCEL_FS_16g = 0x06 # bit[2:1] +REG_VAL_BIT_ACCEL_DLPF = 0x01 # bit[0] + +# user bank 3 register +REG_ADD_I2C_SLV0_ADDR = 0x03 +REG_ADD_I2C_SLV0_REG = 0x04 +REG_ADD_I2C_SLV0_CTRL = 0x05 +REG_VAL_BIT_SLV0_EN = 0x80 +REG_VAL_BIT_MASK_LEN = 0x07 +REG_ADD_I2C_SLV0_DO = 0x06 +REG_ADD_I2C_SLV1_ADDR = 0x07 +REG_ADD_I2C_SLV1_REG = 0x08 +REG_ADD_I2C_SLV1_CTRL = 0x09 +REG_ADD_I2C_SLV1_DO = 0x0A + +# define ICM-20948 Register end + +# define ICM-20948 MAG Register +REG_ADD_MAG_WIA1 = 0x00 +REG_VAL_MAG_WIA1 = 0x48 +REG_ADD_MAG_WIA2 = 0x01 +REG_VAL_MAG_WIA2 = 0x09 +REG_ADD_MAG_ST2 = 0x10 +REG_ADD_MAG_DATA = 0x11 +REG_ADD_MAG_CNTL2 = 0x31 +REG_VAL_MAG_MODE_PD = 0x00 +REG_VAL_MAG_MODE_SM = 0x01 +REG_VAL_MAG_MODE_10HZ = 0x02 +REG_VAL_MAG_MODE_20HZ = 0x04 +REG_VAL_MAG_MODE_50HZ = 0x05 +REG_VAL_MAG_MODE_100HZ = 0x08 +REG_VAL_MAG_MODE_ST = 0x10 +# define ICM-20948 MAG Register end + +MAG_DATA_LEN =6 + +class ICM20948(I2CIOWrapper): + def __init__(self, i2c, address=I2C_ADD_ICM20948): + super().__init__(i2c, address) + + + bRet=self.icm20948Check() #Initialization of the device multiple times after power on will result in a return error + # while true != bRet: + # print("ICM-20948 Error\n" ) + # time.sleep(0.5) + # print("ICM-20948 OK\n" ) + time.sleep(0.5) #We can skip this detection by delaying it by 500 milliseconds + # user bank 0 register + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_0) + self._write_byte( REG_ADD_PWR_MIGMT_1 , REG_VAL_ALL_RGE_RESET) + time.sleep(0.1) + self._write_byte( REG_ADD_PWR_MIGMT_1 , REG_VAL_RUN_MODE) + #user bank 2 register + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_2) + self._write_byte( REG_ADD_GYRO_SMPLRT_DIV , 0x07) + self._write_byte( REG_ADD_GYRO_CONFIG_1 , REG_VAL_BIT_GYRO_DLPCFG_6 | REG_VAL_BIT_GYRO_FS_1000DPS | REG_VAL_BIT_GYRO_DLPF) + self._write_byte( REG_ADD_ACCEL_SMPLRT_DIV_2 , 0x07) + self._write_byte( REG_ADD_ACCEL_CONFIG , REG_VAL_BIT_ACCEL_DLPCFG_6 | REG_VAL_BIT_ACCEL_FS_2g | REG_VAL_BIT_ACCEL_DLPF) + #user bank 0 register + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_0) + time.sleep(0.1) + self.icm20948GyroOffset() + self.icm20948MagCheck() + self.icm20948WriteSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_WRITE,REG_ADD_MAG_CNTL2, REG_VAL_MAG_MODE_20HZ) + + def icm20948_Gyro_Accel_Read(self): + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_0) + data =self._read_block(REG_ADD_ACCEL_XOUT_H, 12) + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_2) + Accel[0] = (data[0]<<8)|data[1] + Accel[1] = (data[2]<<8)|data[3] + Accel[2] = (data[4]<<8)|data[5] + Gyro[0] = ((data[6]<<8)|data[7]) - GyroOffset[0] + Gyro[1] = ((data[8]<<8)|data[9]) - GyroOffset[1] + Gyro[2] = ((data[10]<<8)|data[11]) - GyroOffset[2] + if Accel[0]>=32767: #Solve the problem that Python shift will not overflow + Accel[0]=Accel[0]-65535 + elif Accel[0]<=-32767: + Accel[0]=Accel[0]+65535 + if Accel[1]>=32767: + Accel[1]=Accel[1]-65535 + elif Accel[1]<=-32767: + Accel[1]=Accel[1]+65535 + if Accel[2]>=32767: + Accel[2]=Accel[2]-65535 + elif Accel[2]<=-32767: + Accel[2]=Accel[2]+65535 + if Gyro[0]>=32767: + Gyro[0]=Gyro[0]-65535 + elif Gyro[0]<=-32767: + Gyro[0]=Gyro[0]+65535 + if Gyro[1]>=32767: + Gyro[1]=Gyro[1]-65535 + elif Gyro[1]<=-32767: + Gyro[1]=Gyro[1]+65535 + if Gyro[2]>=32767: + Gyro[2]=Gyro[2]-65535 + elif Gyro[2]<=-32767: + Gyro[2]=Gyro[2]+65535 + def icm20948MagRead(self): + counter=20 + while(counter>0): + time.sleep(0.01) + self.icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ , REG_ADD_MAG_ST2, 1) + if ((pu8data[0] & 0x01)!= 0): + break + counter-=1 + if counter!=0: + for i in range(0,8): + self.icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ , REG_ADD_MAG_DATA , MAG_DATA_LEN) + U8tempX[i] = (pu8data[1]<<8)|pu8data[0] + U8tempY[i] = (pu8data[3]<<8)|pu8data[2] + U8tempZ[i] = (pu8data[5]<<8)|pu8data[4] + Mag[0]=(U8tempX[0]+U8tempX[1]+U8tempX[2]+U8tempX[3]+U8tempX[4]+U8tempX[5]+U8tempX[6]+U8tempX[7])/8 + Mag[1]=-(U8tempY[0]+U8tempY[1]+U8tempY[2]+U8tempY[3]+U8tempY[4]+U8tempY[5]+U8tempY[6]+U8tempY[7])/8 + Mag[2]=-(U8tempZ[0]+U8tempZ[1]+U8tempZ[2]+U8tempZ[3]+U8tempZ[4]+U8tempZ[5]+U8tempZ[6]+U8tempZ[7])/8 + if Mag[0]>=32767: #Solve the problem that Python shift will not overflow + Mag[0]=Mag[0]-65535 + elif Mag[0]<=-32767: + Mag[0]=Mag[0]+65535 + if Mag[1]>=32767: + Mag[1]=Mag[1]-65535 + elif Mag[1]<=-32767: + Mag[1]=Mag[1]+65535 + if Mag[2]>=32767: + Mag[2]=Mag[2]-65535 + elif Mag[2]<=-32767: + Mag[2]=Mag[2]+65535 + def icm20948ReadSecondary(self,u8I2CAddr,u8RegAddr,u8Len): + u8Temp=0 + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + self._write_byte( REG_ADD_I2C_SLV0_ADDR, u8I2CAddr) + self._write_byte( REG_ADD_I2C_SLV0_REG, u8RegAddr) + self._write_byte( REG_ADD_I2C_SLV0_CTRL, REG_VAL_BIT_SLV0_EN|u8Len) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + + u8Temp = self._read_byte(REG_ADD_USER_CTRL) + u8Temp |= REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + time.sleep(0.01) + u8Temp &= ~REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + + for i in range(0,u8Len): + pu8data[i]= self._read_byte( REG_ADD_EXT_SENS_DATA_00+i) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + + u8Temp = self._read_byte(REG_ADD_I2C_SLV0_CTRL) + u8Temp &= ~((REG_VAL_BIT_I2C_MST_EN)&(REG_VAL_BIT_MASK_LEN)) + self._write_byte( REG_ADD_I2C_SLV0_CTRL, u8Temp) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + def icm20948WriteSecondary(self,u8I2CAddr,u8RegAddr,u8data): + u8Temp=0 + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + self._write_byte( REG_ADD_I2C_SLV1_ADDR, u8I2CAddr) + self._write_byte( REG_ADD_I2C_SLV1_REG, u8RegAddr) + self._write_byte( REG_ADD_I2C_SLV1_DO, u8data) + self._write_byte( REG_ADD_I2C_SLV1_CTRL, REG_VAL_BIT_SLV0_EN|1) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + + u8Temp = self._read_byte(REG_ADD_USER_CTRL) + u8Temp |= REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + time.sleep(0.01) + u8Temp &= ~REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + + u8Temp = self._read_byte(REG_ADD_I2C_SLV0_CTRL) + u8Temp &= ~((REG_VAL_BIT_I2C_MST_EN)&(REG_VAL_BIT_MASK_LEN)) + self._write_byte( REG_ADD_I2C_SLV0_CTRL, u8Temp) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + def icm20948GyroOffset(self): + s32TempGx = 0 + s32TempGy = 0 + s32TempGz = 0 + for i in range(0,32): + self.icm20948_Gyro_Accel_Read() + s32TempGx += Gyro[0] + s32TempGy += Gyro[1] + s32TempGz += Gyro[2] + time.sleep(0.01) + GyroOffset[0] = s32TempGx >> 5 + GyroOffset[1] = s32TempGy >> 5 + GyroOffset[2] = s32TempGz >> 5 + def _read_byte(self, cmd): + return self.read(bytes([cmd]))[0] + + def _read_block(self, reg, length=1): + return self.read(bytes([reg]), size=length) + + def _read_u16(self,cmd): + LSB = self.read(bytes([cmd])) + MSB = self.read(bytes([cmd + 1])) + return (MSB << 8) + LSB + + def _write_byte(self,cmd,val): + self.write(bytes([cmd]), bytes([val])) + time.sleep(0.0001) + + def imuAHRSupdate(self,gx, gy,gz,ax,ay,az,mx,my,mz): + norm=0.0 + hx = hy = hz = bx = bz = 0.0 + vx = vy = vz = wx = wy = wz = 0.0 + exInt = eyInt = ezInt = 0.0 + ex=ey=ez=0.0 + halfT = 0.024 + global q0 + global q1 + global q2 + global q3 + q0q0 = q0 * q0 + q0q1 = q0 * q1 + q0q2 = q0 * q2 + q0q3 = q0 * q3 + q1q1 = q1 * q1 + q1q2 = q1 * q2 + q1q3 = q1 * q3 + q2q2 = q2 * q2 + q2q3 = q2 * q3 + q3q3 = q3 * q3 + + norm = float(1/math.sqrt(ax * ax + ay * ay + az * az)) + ax = ax * norm + ay = ay * norm + az = az * norm + + norm = float(1/math.sqrt(mx * mx + my * my + mz * mz)) + mx = mx * norm + my = my * norm + mz = mz * norm + + # compute reference direction of flux + hx = 2 * mx * (0.5 - q2q2 - q3q3) + 2 * my * (q1q2 - q0q3) + 2 * mz * (q1q3 + q0q2) + hy = 2 * mx * (q1q2 + q0q3) + 2 * my * (0.5 - q1q1 - q3q3) + 2 * mz * (q2q3 - q0q1) + hz = 2 * mx * (q1q3 - q0q2) + 2 * my * (q2q3 + q0q1) + 2 * mz * (0.5 - q1q1 - q2q2) + bx = math.sqrt((hx * hx) + (hy * hy)) + bz = hz + + # estimated direction of gravity and flux (v and w) + vx = 2 * (q1q3 - q0q2) + vy = 2 * (q0q1 + q2q3) + vz = q0q0 - q1q1 - q2q2 + q3q3 + wx = 2 * bx * (0.5 - q2q2 - q3q3) + 2 * bz * (q1q3 - q0q2) + wy = 2 * bx * (q1q2 - q0q3) + 2 * bz * (q0q1 + q2q3) + wz = 2 * bx * (q0q2 + q1q3) + 2 * bz * (0.5 - q1q1 - q2q2) + + # error is sum of cross product between reference direction of fields and direction measured by sensors + ex = (ay * vz - az * vy) + (my * wz - mz * wy) + ey = (az * vx - ax * vz) + (mz * wx - mx * wz) + ez = (ax * vy - ay * vx) + (mx * wy - my * wx) + + if (ex != 0.0 and ey != 0.0 and ez != 0.0): + exInt = exInt + ex * Ki * halfT + eyInt = eyInt + ey * Ki * halfT + ezInt = ezInt + ez * Ki * halfT + + gx = gx + Kp * ex + exInt + gy = gy + Kp * ey + eyInt + gz = gz + Kp * ez + ezInt + + q0 = q0 + (-q1 * gx - q2 * gy - q3 * gz) * halfT + q1 = q1 + (q0 * gx + q2 * gz - q3 * gy) * halfT + q2 = q2 + (q0 * gy - q1 * gz + q3 * gx) * halfT + q3 = q3 + (q0 * gz + q1 * gy - q2 * gx) * halfT + + norm = float(1/math.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3)) + q0 = q0 * norm + q1 = q1 * norm + q2 = q2 * norm + q3 = q3 * norm + def icm20948Check(self): + bRet=false + if REG_VAL_WIA == self._read_byte(REG_ADD_WIA): + bRet = true + return bRet + + def icm20948MagCheck(self): + self.icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ,REG_ADD_MAG_WIA1, 2) + if (pu8data[0] == REG_VAL_MAG_WIA1) and ( pu8data[1] == REG_VAL_MAG_WIA2) : + bRet = true + return bRet + def icm20948CalAvgValue(self): + MotionVal[0]=Gyro[0]/32.8 + MotionVal[1]=Gyro[1]/32.8 + MotionVal[2]=Gyro[2]/32.8 + MotionVal[3]=Accel[0] + MotionVal[4]=Accel[1] + MotionVal[5]=Accel[2] + MotionVal[6]=Mag[0] + MotionVal[7]=Mag[1] + MotionVal[8]=Mag[2] + + +if __name__ == '__main__': + import time + from machine import I2C + + print("\nSense HAT Test Program ...\n") + MotionVal=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] + icm20948=ICM20948(I2C(I2C.I2C1, I2C.STANDARD_MODE)) + while True: + try: + icm20948.icm20948_Gyro_Accel_Read() + icm20948.icm20948MagRead() + icm20948.icm20948CalAvgValue() + time.sleep(1) + icm20948.imuAHRSupdate(MotionVal[0] * 0.0175, MotionVal[1] * 0.0175,MotionVal[2] * 0.0175, + MotionVal[3],MotionVal[4],MotionVal[5], + MotionVal[6], MotionVal[7], MotionVal[8]) + pitch = math.asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3 + roll = math.atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3 + yaw = math.atan2(-2 * q1 * q2 - 2 * q0 * q3, 2 * q2 * q2 + 2 * q3 * q3 - 1) * 57.3 + print("\r\n /-------------------------------------------------------------/ \r\n") + print('\r\n Roll = %.2f , Pitch = %.2f , Yaw = %.2f\r\n'%(roll,pitch,yaw)) + print('\r\nAcceleration: X = %d , Y = %d , Z = %d\r\n'%(Accel[0],Accel[1],Accel[2])) + print('\r\nGyroscope: X = %d , Y = %d , Z = %d\r\n'%(Gyro[0],Gyro[1],Gyro[2])) + print('\r\nMagnetic: X = %d , Y = %d , Z = %d'%((Mag[0]),Mag[1],Mag[2])) + except(KeyboardInterrupt): + print("\n") + break diff --git a/BG93-M3-Tracker/code/drivers/lps22hb.py b/BG93-M3-Tracker/code/drivers/lps22hb.py new file mode 100644 index 0000000..d69b0a3 --- /dev/null +++ b/BG93-M3-Tracker/code/drivers/lps22hb.py @@ -0,0 +1,94 @@ +import utime +from usr.libs.i2c import I2CIOWrapper + + +#i2c address +LPS22HB_SLAVE_ADDRESS = 0x5C +# id +LPS22HB_CHIP_ID = 0xB1 + +#Register +LPS_INT_CFG = b"\x0B" # Interrupt register +LPS_THS_P_L = b"\x0C" # Pressure threshold registers +LPS_THS_P_H = b"\x0D" +LPS_WHO_AM_I = b"\x0F" # Who am I +LPS_CTRL_REG1 = b"\x10" # Control registers +LPS_CTRL_REG2 = b"\x11" +LPS_CTRL_REG3 = b"\x12" +LPS_FIFO_CTRL = b"\x14" # FIFO configuration register +LPS_REF_P_XL = b"\x15" # Reference pressure registers +LPS_REF_P_L = b"\x16" +LPS_REF_P_H = b"\x17" +LPS_RPDS_L = b"\x18" # Pressure offset registers +LPS_RPDS_H = b"\x19" +LPS_RES_CONF = b"\x1A" # Resolution register +LPS_INT_SOURCE = b"\x25" # Interrupt register +LPS_FIFO_STATUS = b"\x26" # FIFO status register +LPS_STATUS = b"\x27" # Status register +LPS_PRESS_OUT_XL = b"\x28" # Pressure output registers +LPS_PRESS_OUT_L = b"\x29" +LPS_PRESS_OUT_H = b"\x2A" +LPS_TEMP_OUT_L = b"\x2B" # Temperature output registers +LPS_TEMP_OUT_H = b"\x2C" +LPS_RES = b"\x33" # Filter reset register + + +class Lps22hb(I2CIOWrapper): + + def init(self): + chip_id = self.getChipId() + if chip_id != LPS22HB_CHIP_ID: + raise ValueError("{} got Wrong chip id: 0x{:02X}".format(type(self).__name__, chip_id)) + self.reset() # Wait for reset to complete + self.write(LPS_CTRL_REG1, b"\x02") # Low-pass filter disabled , output registers not updated until MSB and LSB have been read , Enable Block Data Update , Set Output Data Rate to 0 + + def getChipId(self): + return self.read(LPS_WHO_AM_I)[0] + + def reset(self): + data = self.read(LPS_CTRL_REG2)[0] + data |= 0x04 + self.write(LPS_CTRL_REG2, bytes([data])) # SWRESET Set 1 + while data: + data = self.read(LPS_CTRL_REG2)[0] + data &= 0x04 + + def __startOneshot(self): + data = self.read(LPS_CTRL_REG3)[0] + data = self.read(LPS_CTRL_REG2)[0] + data |= 0x01 # ONE_SHOT Set 1 + self.write(LPS_CTRL_REG2, bytes([data])) + + def getTempAndPressure(self): + self.__startOneshot() + for _ in range(10): + status = self.read(LPS_STATUS)[0] + if not (status & 0x01 and status & 0x02): + continue + press_out_xl = self.read(LPS_PRESS_OUT_XL)[0] + press_out_l = self.read(LPS_PRESS_OUT_L)[0] + press_out_h = self.read(LPS_PRESS_OUT_H)[0] + press_data = ((press_out_h << 16) + (press_out_l << 8) + press_out_xl) / 4096.0 + temp_out_l = self.read(LPS_TEMP_OUT_L)[0] + temp_out_h = self.read(LPS_TEMP_OUT_H)[0] + temp_data =((temp_out_h << 8) + temp_out_l) / 100.0 + return round(press_data, 2), round(temp_data, 2) + else: + return 0, 0 + + +if __name__ == '__main__': + print("\nPressure Sensor Test Program ...\n") + from machine import I2C + + lps22hb=Lps22hb(I2C(I2C.I2C1, I2C.STANDARD_MODE), LPS22HB_SLAVE_ADDRESS) + lps22hb.init() + + for i in range(100): + try: + press_data, temp_data = lps22hb.getTempAndPressure() + utime.sleep_ms(100) + print('Pressure: {:6.2f} hPa, Temperature: {:6.2f} °C'.format(press_data or 0, temp_data or 0)) + except Exception as e: + print(e) + break diff --git a/BG93-M3-Tracker/code/drivers/shtc3.py b/BG93-M3-Tracker/code/drivers/shtc3.py new file mode 100644 index 0000000..f44d749 --- /dev/null +++ b/BG93-M3-Tracker/code/drivers/shtc3.py @@ -0,0 +1,103 @@ +""" +SHTC3 temperature and humidity sensor driver. +The SHTC3 is a digital humidity and temperature sensor designed especially for battery-driven high-volume consumer electronics applications. +""" + +import utime +from usr.libs.i2c import I2CIOWrapper + + +SHTC3_SLAVE_ADDR = 0x70 + +# Commands +SHTC3_WAKEUP = b"\x35\x17" +SHTC3_SLEEP = b"\xB0\x98" +SHTC3_NM_CE_READ_TH = b"\x7C\xA2" +SHTC3_NM_CE_READ_RH = b"\x5C\x24" +SHTC3_NM_CD_READ_TH = b"\x78\x66" +SHTC3_NM_CD_READ_RH = b"\x58\xE0" +SHTC3_LM_CE_READ_TH = b"\x64\x58" +SHTC3_LM_CE_READ_RH = b"\x44\xDE" +SHTC3_LM_CD_READ_TH = b"\x60\x9C" +SHTC3_LM_CD_READ_RH = b"\x40\x1A" +SHTC3_SOFTWARE_RESET = b"\x40\x1A" +SHTC3_ID = b"\xEF\xC8" + + +class Shtc3(I2CIOWrapper): + + def init(self): + chip_id = self.getChipId() + if chip_id != 0x0807: + raise ValueError("{} get wrong chip id: {}".format(type(self).__name__, chip_id)) + self.soft_reset() + + def getChipId(self): + id = int.from_bytes(self.read(SHTC3_ID, 2), "big") + return id & 0x0807 + + def wakeup(self): + self.write(SHTC3_WAKEUP, b'') + utime.sleep_ms(30) + + def sleep(self): + self.write(SHTC3_SLEEP, b'') + + def soft_reset(self): + self.write(SHTC3_SOFTWARE_RESET, b'') + utime.sleep_ms(30) + + @staticmethod + def checkCrc(data, checksum): + crc = 0xFF + for one in data: + crc ^= one + for _ in range(8): + if(crc & 0x80): + crc = (crc << 1) ^ 0x131 + else: + crc = crc << 1 + return crc == checksum + + def __getValue(self): + utime.sleep_ms(20) + data = self.read(b'', 3) + if self.checkCrc(data[:2], data[2]): + return data[0] << 8 | data[1] + + def getTempValue(self): + """Calculate the temperature value.""" + self.write(b'', SHTC3_NM_CD_READ_TH) + value = self.__getValue() + if value is not None: + value = 175 * value / 65536.0 - 45.0 + return round(value, 2) + return 0 + + def getHumiValue(self): + """Calculate the humidity value.""" + self.write(b'', SHTC3_NM_CD_READ_RH) + value = self.__getValue() + if value is not None: + value = 100 * value / 65536.0 + return round(value, 2) + return 0 + + def getTempAndHumi(self): + self.wakeup() + temp = self.getTempValue() + humi = self.getHumiValue() + self.sleep() + return temp, humi + + +if __name__ == "__main__": + from machine import I2C + shtc3_dev = Shtc3(I2C(I2C.I2C1, I2C.STANDARD_MODE), SHTC3_SLAVE_ADDR) + shtc3_dev.init() + for i in range(100): + shtc3_dev.wakeup() + temp = shtc3_dev.getTempValue() + humi = shtc3_dev.getHumiValue() + shtc3_dev.sleep() + print("Temperature: {:.2f}°C , Humidity: {:.2f} %\n".format(temp, humi)) diff --git a/BG93-M3-Tracker/code/drivers/tcs34725.py b/BG93-M3-Tracker/code/drivers/tcs34725.py new file mode 100644 index 0000000..e04c43f --- /dev/null +++ b/BG93-M3-Tracker/code/drivers/tcs34725.py @@ -0,0 +1,323 @@ +import utime as time +from usr.libs.i2c import I2CIOWrapper +from machine import ExtInt + + +TCS34725_SLAVE_ADDR = 0x29 + +TCS34725_R_Coef = 0.136 +TCS34725_G_Coef = 1.000 +TCS34725_B_Coef = -0.444 +TCS34725_GA = 1.0 +TCS34725_DF = 310.0 +TCS34725_CT_Coef = 3810.0 +TCS34725_CT_Offset = 1391.0 + +class Tcs34725(I2CIOWrapper): + + Gain_t = 0 + IntegrationTime_t = 0 + + TCS34725_CMD_BIT = 0x80 + TCS34725_CMD_ReadByte = 0x00 + TCS34725_CMD_Read_Word = 0x20 + TCS34725_CMD_Clear_INT = 0x66 + + TCS34725_ENABLE = 0x00 + TCS34725_ENABLE_AIEN = 0x10 # RGBC Interrupt Enable + TCS34725_ENABLE_WEN = 0x08 # Wait enable - Writing 1 activates the wait timer + TCS34725_ENABLE_AEN = 0x02 # RGBC Enable - Writing 1 actives the ADC, 0 disables it + TCS34725_ENABLE_PON = 0x01 # Power on - Writing 1 activates the internal oscillator, 0 disables it + TCS34725_ATIME = 0x01 # Integration time + TCS34725_WTIME = 0x03 # Wait time (if TCS34725_ENABLE_WEN is asserted) + TCS34725_WTIME_2_4MS = 0xFF # WLONG0 = 2.4ms WLONG1 = 0.029s + TCS34725_WTIME_204MS = 0xAB # WLONG0 = 204ms WLONG1 = 2.45s + TCS34725_WTIME_614MS = 0x00 # WLONG0 = 614ms WLONG1 = 7.4s + TCS34725_AILTL = 0x04 # Clear channel lower interrupt threshold + TCS34725_AILTH = 0x05 + TCS34725_AIHTL = 0x06 # Clear channel upper interrupt threshold + TCS34725_AIHTH = 0x07 + TCS34725_PERS = 0x0C # Persistence register - basic SW filtering mechanism for interrupts + TCS34725_PERS_NONE = 0b0000 # Every RGBC cycle generates an interrupt + TCS34725_PERS_1_CYCLE = 0b0001 # 1 clean channel value outside threshold range generates an interrupt + TCS34725_PERS_2_CYCLE = 0b0010 # 2 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_3_CYCLE = 0b0011 # 3 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_5_CYCLE = 0b0100 # 5 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_10_CYCLE = 0b0101 # 10 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_15_CYCLE = 0b0110 # 15 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_20_CYCLE = 0b0111 # 20 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_25_CYCLE = 0b1000 # 25 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_30_CYCLE = 0b1001 # 30 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_35_CYCLE = 0b1010 # 35 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_40_CYCLE = 0b1011 # 40 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_45_CYCLE = 0b1100 # 45 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_50_CYCLE = 0b1101 # 50 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_55_CYCLE = 0b1110 # 55 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_60_CYCLE = 0b1111 # 60 clean channel values outside threshold range generates an interrupt + TCS34725_CONFIG = 0x0D + TCS34725_CONFIG_WLONG = 0x02 # Choose between short and long (12x) wait times via TCS34725_WTIME + TCS34725_CONTROL = 0x0F # Set the gain level for the sensor + TCS34725_ID = 0x12 # 0x44 = TCS34721/TCS34725, 0x4D = TCS34723/TCS34727 + TCS34725_STATUS = 0x13 + TCS34725_STATUS_AINT = 0x10 # RGBC Clean channel interrupt + TCS34725_STATUS_AVALID = 0x01 # Indicates that the RGBC channels have completed an integration cycle + TCS34725_CDATAL = 0x14 # Clear channel data + TCS34725_CDATAH = 0x15 + TCS34725_RDATAL = 0x16 # Red channel data + TCS34725_RDATAH = 0x17 + TCS34725_GDATAL = 0x18 # Green channel data + TCS34725_GDATAH = 0x19 + TCS34725_BDATAL = 0x1A # Blue channel data + TCS34725_BDATAH = 0x1B + + #Integration Time + TCS34725_INTEGRATIONTIME_2_4MS = 0xFF #< 2.4ms - 1 cycle - Max Count: 1024 + TCS34725_INTEGRATIONTIME_24MS = 0xF6 #< 24ms - 10 cycles - Max Count: 10240 + TCS34725_INTEGRATIONTIME_50MS = 0xEB #< 50ms - 20 cycles - Max Count: 20480 + TCS34725_INTEGRATIONTIME_101MS = 0xD5 #< 101ms - 42 cycles - Max Count: 43008 + TCS34725_INTEGRATIONTIME_154MS = 0xC0 #< 154ms - 64 cycles - Max Count: 65535 + TCS34725_INTEGRATIONTIME_700MS = 0x00 #< 700ms - 256 cycles - Max Count: 65535 + + #Gain + TCS34725_GAIN_1X = 0x00 #< No gain */ + TCS34725_GAIN_4X = 0x01 #< 4x gain */ + TCS34725_GAIN_16X = 0x02 #< 16x gain */ + TCS34725_GAIN_60X = 0x03 #< 60x gain */ + + + def __init__(self, i2c, slaveaddr=0x29, debug=False): + super().__init__(i2c, slaveaddr) + self.debug = debug + #Set GPIO mode + self.INT = ExtInt(ExtInt.GPIO29, ExtInt.IRQ_FALLING, ExtInt.PULL_PU, lambda args: print(args)) + self.INT.enable() + if (self.debug): + print("Reseting TSL2581") + + def writeByte(self, reg, value): + # "Writes an 8-bit value to the specified register/address" + reg = reg | self.TCS34725_CMD_BIT # Register addressing highest bit is set to 1 + super().writeByte(reg, value) + if (self.debug): + print("I2C: Write 0x%02X to register 0x%02X" % (value, reg)) + + def readByte(self, reg): + # "Read an unsigned byte from the I2C device" + reg = reg | self.TCS34725_CMD_BIT + result = super().readByte(reg) + if (self.debug): + print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg)) + return result + + def readWord(self, reg): + # "Read an unsigned byte from the I2C device" + reg = reg | self.TCS34725_CMD_BIT + result = int.from_bytes(self.read(bytes([reg]), size=2), "big") + if (self.debug): + print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg)) + return result + + def setGain(self, gain): + self.writeByte(self.TCS34725_CONTROL, gain) + self.Gain_t = gain + + def setIntegrationTime(self, time): + # Update the timing register + self.writeByte(self.TCS34725_ATIME, time) + self.IntegrationTime_t = time + + def enable(self): + self.writeByte(self.TCS34725_ENABLE, self.TCS34725_ENABLE_PON) + time.sleep(0.01) + self.writeByte(self.TCS34725_ENABLE, self.TCS34725_ENABLE_PON | self.TCS34725_ENABLE_AEN) + time.sleep(0.01) + + def disable(self): + #Turn the device off to save power + reg = self.readByte(self.TCS34725_ENABLE) + self.writeByte(self.TCS34725_ENABLE, reg & ~(self.TCS34725_ENABLE_PON | self.TCS34725_ENABLE_AEN)) + + def interruptEnable(self): + reg = self.readByte(self.TCS34725_ENABLE) + self.writeByte(self.TCS34725_ENABLE, reg | self.TCS34725_ENABLE_AIEN) + + def interruptDisable(self): + reg = self.readByte(self.TCS34725_ENABLE) + self.writeByte(self.TCS34725_ENABLE, reg & (~self.TCS34725_ENABLE_AIEN)) + + def Set_Interrupt_Persistence_Reg(self, PER): + if(PER < 0x10): + self.writeByte(self.TCS34725_PERS, PER) + else : + self.writeByte(self.TCS34725_PERS, self.TCS34725_PERS_60_CYCLE) + + def setInterruptThreshold(self, Threshold_H, Threshold_L): + self.writeByte(self.TCS34725_AILTL, Threshold_L & 0xff) + self.writeByte(self.TCS34725_AILTH, Threshold_L >> 8) + self.writeByte(self.TCS34725_AIHTL, Threshold_H & 0xff) + self.writeByte(self.TCS34725_AIHTH, Threshold_H >> 8) + + def clearInterruptFlag(self): + self.writeByte(self.TCS34725_CMD_Clear_INT, 0x00) + + def init(self): + chip_id = self.readByte(self.TCS34725_ID) + if chip_id not in (0x44, 0x4D): + raise ValueError("Device ID is not correct") + self.setIntegrationTime(self.TCS34725_INTEGRATIONTIME_154MS) + self.setGain(self.TCS34725_GAIN_60X) + self.IntegrationTime_t = self.TCS34725_INTEGRATIONTIME_154MS + self.Gain_t = self.TCS34725_GAIN_60X + self.enable() + self.interruptEnable() + + + def getLuxInterrupt(self, Threshold_H, Threshold_L): + self.setInterruptThreshold(Threshold_H, Threshold_L) + if(self.INT.read_level() == 0): + self.clearInterruptFlag() + self.Set_Interrupt_Persistence_Reg(self.TCS34725_PERS_2_CYCLE) + return 1 + + return 0 + + def getChipId(self): + return self.readByte(self.TCS34725_ID) + + def getRGBData(self): + self.C = self.readWord(self.TCS34725_CDATAL | self.TCS34725_CMD_Read_Word) + self.R = self.readWord(self.TCS34725_RDATAL | self.TCS34725_CMD_Read_Word) + self.G = self.readWord(self.TCS34725_GDATAL | self.TCS34725_CMD_Read_Word) + self.B = self.readWord(self.TCS34725_BDATAL | self.TCS34725_CMD_Read_Word) + if(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_2_4MS): + time.sleep(0.01) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_24MS): + time.sleep(0.04) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_50MS): + time.sleep(0.05) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_101MS): + time.sleep(0.1) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_154MS): + time.sleep(0.2) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_700MS): + time.sleep(0.7) + + #Convert read data to RGB888 format + def getRGB888(self): + i = 1 + if(self.R >= self.G and self.R >= self.B): + i = self.R // 255 + 1 + + elif( self.G >= self.R and self.G >= self.B): + i = self.G // 255 + 1 + + elif( self.B >= self.G and self.B >= self.R): + i = self.B // 255 + 1 + + if(i!=0): + self.RGB888_R = self.R // i + self.RGB888_G = self.G // i + self.RGB888_B = self.B // i + + if(self.RGB888_R > 30): + self.RGB888_R = self.RGB888_R - 30 + if(self.RGB888_G > 30): + self.RGB888_G = self.RGB888_G - 30 + if(self.RGB888_B > 30): + self.RGB888_B = self.RGB888_B - 30 + + self.RGB888_R = self.RGB888_R * 255 // 225 + self.RGB888_G = self.RGB888_G * 255 // 225 + self.RGB888_B = self.RGB888_B * 255 // 225 + self.RGB888 = (self.RGB888_R<<16) | (self.RGB888_G<<8) | (self.RGB888_B) + + def getRGB565(self): + i = 1 + RGB565_R = 0 + RGB565_G = 0 + RGB565_B = 0 + + if(self.R >= self.G and self.R >= self.B): + i = self.R // 255 + 1 + elif( self.G >= self.R and self.G >= self.B): + i = self.G // 255 + 1 + elif( self.B >= self.G and self.B >= self.R): + i = self.B // 255 + 1 + + if(i!=0): + RGB565_R = self.R // i + RGB565_G = self.G // i + RGB565_B = self.B // i + + if(RGB565_R > 30): + RGB565_R = RGB565_R - 30 + if(RGB565_G > 30): + RGB565_G = RGB565_G - 30 + if(RGB565_B > 30): + RGB565_B = RGB565_B - 30 + + RGB565_R = RGB565_R * 255 // 225 + RGB565_G = RGB565_G * 255 // 225 + RGB565_B = RGB565_B * 255 // 225 + self.RG565 = (((RGB565_R>>3) << 11) | ((RGB565_G>>2) << 5) | (RGB565_B>>3 ))&0xffff + + def getLux(self): + atime_ms = ((256 - self.IntegrationTime_t) * 2.4) + if(self.R + self.G + self.B > self.C): + ir = (self.R + self.G + self.B - self.C) / 2 + else: + ir = 0 + r_comp = self.R - ir + g_comp = self.G - ir + b_comp = self.B - ir + Gain_temp = 1 + if(self.Gain_t == self.TCS34725_GAIN_1X): + Gain_temp = 1 + elif(self.Gain_t == self.TCS34725_GAIN_4X): + Gain_temp = 4 + elif(self.Gain_t == self.TCS34725_GAIN_16X): + Gain_temp = 16 + elif(self.Gain_t == self.TCS34725_GAIN_60X): + Gain_temp = 60 + + cpl = (atime_ms * Gain_temp) / (TCS34725_GA * TCS34725_DF) + lux = (TCS34725_R_Coef * (float)(r_comp) + TCS34725_G_Coef * \ + (float)(g_comp) + TCS34725_B_Coef * (float)(b_comp)) / cpl + return lux + + def getColorTemp(self): + ir=1.0 + if(self.R + self.G + self.B > self.C): + ir = (self.R + self.G + self.B - self.C - 1) / 2 + else: + ir = 0 + r_comp = self.R - ir + b_comp = self.B - ir + cct=TCS34725_CT_Coef * (float)(b_comp) / (float)(r_comp) + TCS34725_CT_Offset + return cct + + def getRGBValue(self): + self.getRGBData() + self.getRGB888() + return self.RGB888 + + +if __name__ == "__main__": + from machine import I2C + tcs34725 = Tcs34725(I2C(I2C.I2C1, I2C.STANDARD_MODE), TCS34725_SLAVE_ADDR) + tcs34725.init() + + time.sleep(2) + for _ in range(20): + tcs34725.getRGBData() + tcs34725.getRGB888() + tcs34725.getRGB565() + print("R: %d " % tcs34725.RGB888_R, end="") + print("G: %d " % tcs34725.RGB888_G, end="") + print("B: %d " % tcs34725.RGB888_B, end="") + print("C: %#x " % tcs34725.C, end="") + print("RGB565: %#x " % tcs34725.RG565, end="") + print("RGB888: %#x " % tcs34725.RGB888, end="") + print("LUX: %d " % tcs34725.getLux(), end="") + print("CT: %dK " % tcs34725.getColorTemp(), end="") + print("INT: %d " % tcs34725.getLuxInterrupt(0xff00, 0x00ff)) diff --git a/BG93-M3-Tracker/code/extensions/__init__.py b/BG93-M3-Tracker/code/extensions/__init__.py new file mode 100644 index 0000000..81bf178 --- /dev/null +++ b/BG93-M3-Tracker/code/extensions/__init__.py @@ -0,0 +1,10 @@ +from .qth_client import QthClient +# from .gnss_service import GnssService +from .lbs_service import LbsService +from .sensor_service import SensorService + + +qth_client = QthClient() +# gnss_service = GnssService() +lbs_service = LbsService() +sensor_service = SensorService() diff --git a/BG93-M3-Tracker/code/extensions/gnss_service.py b/BG93-M3-Tracker/code/extensions/gnss_service.py new file mode 100644 index 0000000..186c6f3 --- /dev/null +++ b/BG93-M3-Tracker/code/extensions/gnss_service.py @@ -0,0 +1,225 @@ +import utime +import quecgnss +from usr.libs import CurrentApp +from usr.libs.threading import Thread +from usr.libs.logging import getLogger +import _thread +from .import qth_client +try: + from math import sin, asin, cos, radians, fabs, sqrt +except: + from cmath import sin as csin, cos as ccos, pi + + def radians(x): + return x * pi / 180.0 + + def fabs(x): + return x if x > 0 else -x + + def sin(x): + return csin(x).real + + def cos(x): + return ccos(x).real + + def asin(x): + low, high = -1, 1 + while abs(high - low) > 1e-10: # 精度控制 + mid = (low + high) / 2.0 + if sin(mid) < x: + low = mid + else: + high = mid + return (low + high) / 2.0 + + +logger = getLogger(__name__) + + +EARTH_RADIUS = 6371 # 地球平均半径大约6371km +GLOBAL_DISTANCE = 0 # 里程km + + +def hav(theta): + s = sin(theta / 2) + return s * s + + +def gps_distance(lat0, lng0, lat1, lng1): + # 用haversine公式计算球面两点间的距离 + # 经纬度转换成弧度 + lat0 = radians(lat0) + lat1 = radians(lat1) + lng0 = radians(lng0) + lng1 = radians(lng1) + dlng = fabs(lng0 - lng1) + dlat = fabs(lat0 - lat1) + h = hav(dlat) + cos(lat0) * cos(lat1) * hav(dlng) + distance = 2 * EARTH_RADIUS * asin(pow(h, 0.5)) # km + # distance = int(distance * 1000) # m + return distance + + +class NmeaDict(dict): + + @classmethod + def load(cls, raw): + items = {} + for line in raw.split('\r\n'): + try: + tail_index = line.rfind('*') + if tail_index == -1: + continue + head_index = line.rfind('$', 0, tail_index) + if head_index == -1: + continue + crc = int(line[tail_index + 1:tail_index + 3], 16) + if cls.checksum(line[head_index + 1:tail_index]) != crc: + raise ValueError('CRC check failed') + cmdlist = line[head_index:tail_index].split(',') + # print(line[head_index:]) + if cmdlist[0] not in items: + items[cmdlist[0]] = [] + items[cmdlist[0]].append(line) + except Exception as e: + # logger.debug('parse nmea line error: {}; pass it: {}'.format(e, line)) + continue + return cls(items) + + @staticmethod + def checksum(data): + crc = ord(data[0]) + for one in (ord(_) for _ in data[1:]): + crc ^= one + return crc + + +class GnssService(object): + + def __init__(self, app=None): + self.__gnss = quecgnss + if app is not None: + self.init_app(app) + + def __str__(self): + return '{}'.format(type(self).__name__) + + def init_app(self, app): + app.register('gnss_service', self) + + def load(self): + logger.info('loading {} extension, init quecgnss will take some seconds'.format(self)) + result = self.init() + logger.info('{} init gnss res: {}'.format(self, result)) + if result: + Thread(target=self.start_update).start() + + def init(self): + if self.__gnss.init() != 0: + logger.warn('{} gnss init FAILED'.format(self)) + return False + return True + + def status(self): + # 0 int GNSS模块处于关闭状态 + # 1 int GNSS模块固件升级中 + # 2 int GNSS模块定位中,这种模式下即可开始读取GNSS定位数据,定位数据是否有效需要用户获取到定位数据后,解析对应语句来判断,比如判断GNRMC语句的status是 A 还是 V,A 表示定位有效,V表示定位无效。 + return self.__gnss.get_state() + + def enable(self, flag=True): + return self.__gnss.gnssEnable(bool(flag)) == 0 + + def read(self, size=4096): + raw = self.__gnss.read(size) + if raw != -1: + size, data = raw + # logger.debug('gnss read raw {} bytes data:\n{}'.format(size, data)) + return NmeaDict.load(data) + + def start_update(self): + prev_lat_and_lng = None + + while True: + nmea_dict = self.read() + if nmea_dict is None: + utime.sleep(3) + continue + + nmea_data = None + + if nmea_data is None: + if "$GNRMC" in nmea_dict: + for temp in nmea_dict["$GNRMC"]: + nmea_tuple = temp.split(",") + if nmea_tuple[2] == "A": + nmea_data = temp + + lat_string = nmea_tuple[3] + lat_high = float(lat_string[:2]) + lat_low = float(lat_string[2:]) / 60 + lat = lat_high + lat_low + if nmea_tuple[4] == "S": + lat = -lat + + lng_string = nmea_tuple[5] # 11755.787896484374(单位:分) + lng_high = float(lng_string[:3]) + lng_low = float(lng_string[3:]) / 60 + lng = lng_high + lng_low + if nmea_tuple[6] == "W": + lng = -lng + + break + + if nmea_data is None: + if "$GNGGA" in nmea_dict: + for temp in nmea_dict["$GNGGA"]: + nmea_tuple = temp.split(",") + if nmea_tuple[6] != "0": + nmea_data = temp + + lat_string = nmea_tuple[2] + lat_high = float(lat_string[:2]) + lat_low = float(lat_string[2:]) / 60 + lat = lat_high + lat_low + if nmea_tuple[3] == "S": + lat = -lat + + lng_string = nmea_tuple[4] # 11755.787896484374(单位:分) + lng_high = float(lng_string[:3]) + lng_low = float(lng_string[3:]) / 60 + lng = lng_high + lng_low + if nmea_tuple[5] == "W": + lng = -lng + + break + + if nmea_data is not None: + # logger.debug("GPS data: {}".format(nmea_data)) + # logger.debug("prev_lat_and_lng: {}".format(prev_lat_and_lng)) + logger.debug("lat_and_lng: {}".format((lat, lng))) + if prev_lat_and_lng is None: + # 首次定位 + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendGnss(nmea_data): + prev_lat_and_lng = (lat, lng) + logger.error("send gnss to qth server success") + break + else: + logger.error("send gnss to qth server fail") + else: + # 或者位移超过 50m,则上报 + distance = gps_distance(prev_lat_and_lng[0], prev_lat_and_lng[1], lat, lng) + logger.debug('distance delta: {:f}'.format(distance)) + if distance >= 0.05: + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendGnss(nmea_data): + prev_lat_and_lng = (lat, lng) + logger.error("send gnss to qth server success") + break + else: + logger.error("send gnss to qth server fail") + utime.sleep(3) + + diff --git a/BG93-M3-Tracker/code/extensions/lbs_service.py b/BG93-M3-Tracker/code/extensions/lbs_service.py new file mode 100644 index 0000000..1abac26 --- /dev/null +++ b/BG93-M3-Tracker/code/extensions/lbs_service.py @@ -0,0 +1,94 @@ +import net +import utime +from usr.libs import CurrentApp +from usr.libs.threading import Thread +from usr.libs.logging import getLogger +import _thread + +logger = getLogger(__name__) + + +class LbsService(object): + + def __init__(self, app=None): + self.__net = net + if app is not None: + self.init_app(app) + + def __str__(self): + return '{}'.format(type(self).__name__) + + + def init_app(self, app): + app.register('lbs_service', self) + + def load(self): + logger.info('loading {} extension, init lbs will take some seconds'.format(self)) + Thread(target=self.start_update).start() + + def read(self): + cell_info = net.getCellInfo() + if cell_info != -1 and cell_info[2]: + first_tuple = cell_info[2] + mcc_decimal = first_tuple[0][2] # 获取十进制MCC (如1120) + mcc_hex = "{:x}".format(mcc_decimal).upper() # 转换为十六进制 (如'460') + + lbs_data = "$LBS,{},{},{},{},{},0*69;".format( + mcc_hex, + first_tuple[0][3], + first_tuple[0][5], + first_tuple[0][1], + first_tuple[0][7] + ) + # lbs_data = "$LBS,460,0,15419,128230431,78,0*69;" + return lbs_data + + def start_update(self): + while True: + lbs_data = self.read() + if lbs_data is None: + utime.sleep(2) + continue + + state = CurrentApp().qth_client.isStatusOk() + logger.debug('qth state {}'.format(state)) + if not state: + logger.error('qth client status error, not update lbs data') + else: + logger.debug('qth client status ok, start updata lbs data') + + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendLbs(lbs_data): + break + else: + logger.debug("send lbs data to qth server fail, next report will be after 2 seconds") + utime.sleep(2) + continue + + # logger.debug("send lbs data to qth server success, next report will be after 1800 seconds") + # utime.sleep(1800) + logger.debug("send lbs data to qth server success, next report will be after 10 seconds") + utime.sleep(10) + + def put_lbs(self): + while True: + lbs_data = self.read() + if lbs_data is None: + utime.sleep(2) + continue + + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendLbs(lbs_data): + break + else: + logger.debug("send lbs data to qth server fail, next report will be after 2 seconds") + utime.sleep(2) + continue + + logger.debug("send LBS data to qth server success") + break + + + diff --git a/BG93-M3-Tracker/code/extensions/qth_client.py b/BG93-M3-Tracker/code/extensions/qth_client.py new file mode 100644 index 0000000..0038549 --- /dev/null +++ b/BG93-M3-Tracker/code/extensions/qth_client.py @@ -0,0 +1,120 @@ +from usr import Qth +from usr.libs import CurrentApp +try: + from libs.threading import Lock + from libs.logging import getLogger +except ImportError: + from usr.libs.threading import Lock + from usr.libs.logging import getLogger + +from . import lbs_service +logger = getLogger(__name__) + + +class QthClient(object): + + def __init__(self, app=None): + self.opt_lock = Lock() + if app: + self.init_app(app) + + def __enter__(self): + self.opt_lock.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.opt_lock.release() + + def init_app(self, app): + app.register("qth_client", self) + Qth.init() + Qth.setProductInfo(app.config["QTH_PRODUCT_KEY"], app.config["QTH_PRODUCT_SECRET"]) + Qth.setServer(app.config["QTH_SERVER"]) + Qth.setEventCb( + { + "devEvent": self.eventCallback, + "recvTrans": self.recvTransCallback, + "recvTsl": self.recvTslCallback, + "readTsl": self.readTslCallback, + "readTslServer": self.recvTslServerCallback, + "ota": { + "otaPlan":self.otaPlanCallback, + "fotaResult":self.fotaResultCallback + } + } + ) + + def load(self): + self.start() + + def start(self): + Qth.start() + + def stop(self): + Qth.stop() + def sendTsl(self, mode, value): + return Qth.sendTsl(mode, value) + + def isStatusOk(self): + return Qth.state() + + def sendLbs(self, lbs_data): + return Qth.sendOutsideLocation(lbs_data) + + def sendGnss(self, nmea_data): + return Qth.sendOutsideLocation(nmea_data) + + def eventCallback(self, event, result): + logger.info("dev event:{} result:{}".format(event, result)) + if(2== event and 0 == result): + Qth.otaRequest() + + def recvTransCallback(self, value): + ret =Qth.sendTrans(1, value) + logger.info("recvTrans value:{} ret:{}".format(value, ret)) + + def recvTslCallback(self, value): + logger.info("recvTsl:{}".format(value)) + for cmdId, val in value.items(): + logger.info("recvTsl {}:{}".format(cmdId, val)) + def readTslCallback(self, ids, pkgId): + logger.info("readTsl ids:{} pkgId:{}".format(ids, pkgId)) + value=dict() + + temp1, humi =CurrentApp().sensor_service.get_temp1_and_humi() + press, temp2 = CurrentApp().sensor_service.get_press_and_temp2() + r,g,b = CurrentApp().sensor_service.get_rgb888() + + + for id in ids: + if 3 == id: + value[3]=temp1 + elif 4 == id: + value[4]=humi + elif 5 == id: + value[5]=temp2 + elif 6 == id: + value[6]=press + elif 7 == id: + value[7]={1:r, 2:g, 3:b} + Qth.ackTsl(1, value, pkgId) + + + def recvTslServerCallback(self, serverId, value, pkgId): + logger.info("recvTslServer serverId:{} value:{} pkgId:{}".format(serverId, value, pkgId)) + Qth.ackTslServer(1, serverId, value, pkgId) + + def otaPlanCallback(self, plans): + logger.info("otaPlan:{}".format(plans)) + Qth.otaAction(1) + + def fotaResultCallback(self, comp_no, result): + logger.info("fotaResult comp_no:{} result:{}".format(comp_no, result)) + + def sotaInfoCallback(self, comp_no, version, url, md5, crc): + logger.info("sotaInfo comp_no:{} version:{} url:{} md5:{} crc:{}".format(comp_no, version, url, md5, crc)) + # 当使用url下载固件完成,且MCU更新完毕后,需要获取MCU最新的版本信息,并通过setMcuVer进行更新 + Qth.setMcuVer("MCU1", "V1.0.0", self.sotaInfoCallback, self.sotaResultCallback) + + def sotaResultCallback(comp_no, result): + logger.info("sotaResult comp_no:{} result:{}".format(comp_no, result)) diff --git a/BG93-M3-Tracker/code/extensions/sensor_service.py b/BG93-M3-Tracker/code/extensions/sensor_service.py new file mode 100644 index 0000000..3e306ce --- /dev/null +++ b/BG93-M3-Tracker/code/extensions/sensor_service.py @@ -0,0 +1,144 @@ +import utime +from machine import I2C +from usr.libs import CurrentApp +from usr.libs.threading import Thread +from usr.libs.logging import getLogger +from usr.drivers.shtc3 import Shtc3, SHTC3_SLAVE_ADDR +from usr.drivers.lps22hb import Lps22hb, LPS22HB_SLAVE_ADDRESS +from usr.drivers.tcs34725 import Tcs34725, TCS34725_SLAVE_ADDR + + +logger = getLogger(__name__) + + +class SensorService(object): + + def __init__(self, app=None): + # i2c channel 0 + self.i2c_channel0 = I2C(I2C.I2C1, I2C.STANDARD_MODE) + # SHTC3 + self.shtc3 = Shtc3(self.i2c_channel0, SHTC3_SLAVE_ADDR) + self.shtc3.init() + # LPS22HB + self.lps22hb = Lps22hb(self.i2c_channel0, LPS22HB_SLAVE_ADDRESS) + self.lps22hb.init() + # TCS34725 + self.tcs34725 = Tcs34725(self.i2c_channel0, TCS34725_SLAVE_ADDR) + self.tcs34725.init() + + if app is not None: + self.init_app(app) + + def __str__(self): + return '{}'.format(type(self).__name__) + + def init_app(self, app): + app.register('sensor_service', self) + + def load(self): + logger.info('loading {} extension, init sensors will take some seconds'.format(self)) + Thread(target=self.start_update).start() + + + def get_temp1_and_humi(self): + return self.shtc3.getTempAndHumi() + + def get_press_and_temp2(self): + return self.lps22hb.getTempAndPressure() + def get_rgb888(self): + rgb888 = self.tcs34725.getRGBValue() + logger.debug("R: {}, G: {}, B: {}".format((rgb888 >> 16) & 0xFF, (rgb888 >> 8) & 0xFF, rgb888 & 0xFF)) + + r = (rgb888 >> 16) & 0xFF + g = (rgb888 >> 8) & 0xFF + b = rgb888 & 0xFF + return r, g, b + + def start_update(self): + prev_temp1 = None + prev_humi = None + prev_press = None + prev_temp2 = None + prev_rgb888 = None + + + while True: + data = {} + + try: + temp1, humi = self.shtc3.getTempAndHumi() + logger.debug("temp1: {:0.2f}, humi: {:0.2f}".format(temp1, humi)) + + if prev_temp1 is None or abs(prev_temp1 - temp1) > 1: + data.update({3: round(temp1, 2)}) + prev_temp1 = temp1 + + if prev_humi is None or abs(prev_humi - humi) > 1: + data.update({4: round(humi, 2)}) + prev_humi = humi + + except Exception as e: + logger.error("getTempAndHumi error:{}".format(e)) + + utime.sleep_ms(100) + + try: + press, temp2 = self.lps22hb.getTempAndPressure() + logger.debug("press: {:0.2f}, temp2: {:0.2f}".format(press, temp2)) + + if prev_temp2 is None or abs(prev_temp2 - temp2) > 1: + data.update({5: round(temp2, 2)}) + prev_temp2 = temp2 + + if prev_press is None or abs(prev_press - press) > 1: + data.update({6: round(press, 2)}) + prev_press = press + + except Exception as e: + logger.error("getTempAndPressure error:{}".format(e)) + + utime.sleep_ms(100) + + try: + rgb888 = self.tcs34725.getRGBValue() + logger.debug("R: {}, G: {}, B: {}".format((rgb888 >> 16) & 0xFF, (rgb888 >> 8) & 0xFF, rgb888 & 0xFF)) + + r = (rgb888 >> 16) & 0xFF + g = (rgb888 >> 8) & 0xFF + b = rgb888 & 0xFF + + if prev_rgb888 is None: + data.update({7: {1: r, 2: g, 3: b}}) + prev_rgb888 = rgb888 + else: + prev_r = (prev_rgb888 >> 16) & 0xFF + dr = abs(r - prev_r) + + prev_g = (prev_rgb888 >> 8) & 0xFF + dg = abs(g - prev_g) + + prev_b = prev_rgb888 & 0xFF + db = abs(b - prev_b) + + # 色差超过 150 即认为颜色有变化 + if pow(sum((dr*dr, dg*dg, db*db)), 0.5) >= 150: + data.update({7: {1: r, 2: g, 3: b}}) + prev_rgb888 = rgb888 + + except Exception as e: + logger.error("getRGBValue error:{}".format(e)) + + if data: + with CurrentApp().qth_client: + for _ in range(3): + if CurrentApp().qth_client.sendTsl(1, data): + break + utime.sleep(1) + else: + prev_temp1 = None + prev_humi = None + prev_press = None + prev_temp2 = None + prev_rgb888 = None + + utime.sleep(1) diff --git a/BG93-M3-Tracker/code/libs/__init__.py b/BG93-M3-Tracker/code/libs/__init__.py new file mode 100644 index 0000000..b4838d8 --- /dev/null +++ b/BG93-M3-Tracker/code/libs/__init__.py @@ -0,0 +1,96 @@ +import sys +import net +import sim +import modem +from misc import Power +from .common import Storage +from .collections import OrderedDict, Singleton + + +@Singleton +class _AppCtxGlobals(object): + + def setDefault(self, name, value): + if hasattr(self, name): + return getattr(self, name) + else: + setattr(self, name, value) + return value + + def get(self, name, default=None): + if hasattr(self, name): + return getattr(self, name) + else: + return default + + def set(self, name, value): + setattr(self, name, value) + + +G = _AppCtxGlobals + + +@Singleton +class Application(object): + """Application Class""" + + def __init__(self, name, version='1.0.0'): + self.__name = name + self.config = Storage() + self.__version = version + self.__extensions = OrderedDict() + + def __repr__(self): + return '{}(name=\"{}\", version=\"{}\")'.format(type(self).__name__, self.name, self.version) + + def __getattr__(self, name): + return self.__extensions[name] + + def register(self, name, ext): + if name in self.__extensions: + raise ValueError('extension name \"{}\" already in use'.format(name)) + self.__extensions[name] = ext + + def __powerOnPrintOnce(self): + output = '==================================================\r\n' + output += 'APP_NAME : {}\r\n' + output += 'APP_VERSION : {}\r\n' + output += 'FIRMWARE_VERSION : {}\r\n' + output += 'POWERON_REASON : {}\r\n' + output += 'DEVICE_IMEI : {}\r\n' + output += 'SIM_STATUS : {}\r\n' + output += 'NET_STATUS : {}\r\n' + output += '==================================================' + print(output.format( + self.name, + self.version, + modem.getDevFwVersion(), + Power.powerOnReason(), + modem.getDevImei(), + sim.getStatus(), + net.getState()[1][0] + )) + + def __loadExtensions(self): + for ext in self.__extensions.values(): + if not hasattr(ext, 'load'): + continue + try: + ext.load() + except Exception as e: + sys.print_exception(e) + + def run(self): + self.__powerOnPrintOnce() + self.__loadExtensions() + + @property + def version(self): + return self.__version + + @property + def name(self): + return self.__name + + +CurrentApp = Application diff --git a/BG93-M3-Tracker/code/libs/collections.py b/BG93-M3-Tracker/code/libs/collections.py new file mode 100644 index 0000000..31b619b --- /dev/null +++ b/BG93-M3-Tracker/code/libs/collections.py @@ -0,0 +1,199 @@ + +class Singleton(object): + + def __init__(self, cls): + self.cls = cls + self.instance = None + + def __call__(self, *args, **kwargs): + if self.instance is None: + self.instance = self.cls(*args, **kwargs) + return self.instance + + def __repr__(self): + return repr(self.cls) + + +class _Node(object): + + def __init__(self, obj, next_=None, prev=None): + self.obj = obj + self.next = next_ + self.prev = prev + + def __repr__(self): + return '{}(obj={})'.format(type(self).__name__, repr(self.obj)) + + +class DoubleLinkList(object): + + def __init__(self): + self.__root = _Node(None) + self.__root.next = self.__root + self.__root.prev = self.__root + + def __iter__(self): + curr = self.__root.next + while curr != self.__root: + yield curr + curr = curr.next + + def __len__(self): + result = 0 + for _ in self: + result += 1 + return result + + def is_empty(self): + return self.__root.next is None + + def add(self, obj): + """头插""" + node = _Node(obj, next_=self.__root.next, prev=self.__root) + self.__root.next.prev = node + self.__root.next = node + return node + + def append(self, obj): + """尾插""" + node = _Node(obj, next_=self.__root, prev=self.__root.prev) + self.__root.prev.next = node + self.__root.prev = node + return node + + def insert(self, obj1, obj2): + """指定位置插入(将obj2插入obj1前) + + :param obj1: 节点数据域元素 + :param obj2: 节点数据域元素 + :return: obj2所属节点_Node + """ + pos = self.search(obj2) + if pos is None: + raise ValueError('{} not in list'.format(obj1)) + node = _Node(obj2, next_=pos, prev=pos.prev) + pos.prev.next = node + pos.prev = node + return node + + def search(self, obj): + """查找data所属节点,obj的类型必须实现__eq__ + + :param obj: 链表节点数据域元素 + :return: _Node节点 或 None + """ + for node in self: + if node.obj == obj: + return node + + def remove(self, obj): + """删除obj所属节点 + + :param obj: 链表节点数据域元素 + :return: None + :raise: 元素节点不存在抛ValueError + """ + node = self.search(obj) + if node is None: + raise ValueError('{} not in link'.format(obj)) + node.prev.next = node.next + node.next.prev = node.prev + + +class OrderedDict(object): + + def __init__(self, iterable=None): + self.__keys_link = DoubleLinkList() + self.__key_node_map = {} + self.__storage = {} + if isinstance(iterable, (tuple, list)): + self.__load(iterable) + + def __load(self, sequence): + for k, v in sequence: + self[k] = v + + def __repr__(self): + return '{}({})'.format(type(self).__name__, [(k, v) for k, v in self.items()]) + + def __iter__(self): + return (node.obj for node in self.__keys_link) + + def __setitem__(self, key, value): + if key not in self.__storage: + self.__key_node_map[key] = self.__keys_link.append(key) + self.__storage[key] = value + + def __getitem__(self, item): + return self.__storage[item] + + def __delitem__(self, key): + del self.__storage[key] + node = self.__key_node_map.pop(key) + node.prev.next = node.next + node.next.prev = node.prev + + def keys(self): + return iter(self) + + def values(self): + return (self.__storage[key] for key in self) + + def items(self): + return ((k, self.__storage[k]) for k in self) + + def get(self, key, default=None): + if key not in self.__storage: + return default + return self.__storage[key] + + def pop(self, key, default=None): + if key not in self.__storage: + return default + temp = self[key] + del self[key] + return temp + + def update(self, obj): + for k, v in obj.items(): + self[k] = v + + def setdefault(self, key, value): + if key in self.__storage: + return self[key] + else: + self[key] = value + return value + + +class Integer(object): + """serialize signed/unsigned Integer to/from bytes""" + + def __init__(self, value): + self.__value = value + + @property + def value(self): + return self.__value + + def toBytes(self, length=1, byteorder='big', signed=False): + if byteorder == 'little': + order = range(length) + elif byteorder == 'big': + order = reversed(range(length)) + else: + raise ValueError("byteorder must be either 'little' or 'big'") + return bytes((self.value >> i * 8) & 0xff for i in order) + + @classmethod + def fromBytes(cls, raw, byteorder='big', signed=False): + if byteorder == 'little': + little_ordered = list(raw) + elif byteorder == 'big': + little_ordered = list(reversed(raw)) + else: + raise ValueError("byteorder must be either 'little' or 'big'") + n = sum(b << i * 8 for i, b in enumerate(little_ordered)) + if signed and little_ordered and (little_ordered[-1] & 0x80): + n -= 1 << 8 * len(little_ordered) + return n diff --git a/BG93-M3-Tracker/code/libs/common.py b/BG93-M3-Tracker/code/libs/common.py new file mode 100644 index 0000000..bf9671e --- /dev/null +++ b/BG93-M3-Tracker/code/libs/common.py @@ -0,0 +1,48 @@ +import ql_fs +from .threading import Lock + + +def deepcopy(obj): + if isinstance(obj, (int, float, str, bool, type(None))): + return obj + if isinstance(obj, (list, tuple, set)): + return type(obj)((deepcopy(item) for item in obj)) + elif isinstance(obj, dict): + return {k: deepcopy(v) for k, v in obj.items()} + else: + raise TypeError('unsupported for \"{}\" type'.format(type(obj))) + + +class Storage(dict): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__lock__ = Lock() + self.__storage_path__ = None + + def __enter__(self): + self.__lock__.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.__lock__.release() + + def __from_json(self, path): + if self.__storage_path__ is not None: + raise ValueError('storage already init from \"{}\"'.format(self.__storage_path__)) + if not ql_fs.path_exists(path): + ql_fs.touch(path, {}) + else: + self.update(ql_fs.read_json(path)) + + def init(self, path): + if path.endswith('.json'): + self.__from_json(path) + else: + raise ValueError('\"{}\" file type not supported'.format(path)) + self.__storage_path__ = path + + def save(self): + if self.__storage_path__ is None: + raise ValueError('storage path not existed, did you init?') + ql_fs.touch(self.__storage_path__, self) diff --git a/BG93-M3-Tracker/code/libs/i2c.py b/BG93-M3-Tracker/code/libs/i2c.py new file mode 100644 index 0000000..db59174 --- /dev/null +++ b/BG93-M3-Tracker/code/libs/i2c.py @@ -0,0 +1,44 @@ +from machine import I2C +from usr.libs.collections import Integer + + +class I2CIOWrapper(object): + + class I2CReadError(Exception): + pass + + class I2CWriteError(Exception): + pass + + def __init__(self, i2c, slaveaddr): + if not isinstance(i2c, I2C): + raise TypeError('`i2c` should be machine.I2C type') + self.__i2c = i2c + self.__slaveaddr = slaveaddr + + + def read(self, addr, size=1, delay=0): + if size <= 0: + raise ValueError('`size` should be greater than 0') + data = bytearray(size) + if self.__i2c.read(self.__slaveaddr, addr, len(addr), data, size, delay) != 0: + raise self.I2CReadError("slave 0x{:X} read failed".format(self.__slaveaddr)) + return data + + def write(self, addr, data): + if not isinstance(data, (bytearray, bytes)): + raise TypeError('`data` should be bytearray or bytes') + if self.__i2c.write(self.__slaveaddr, addr, len(addr), data, len(data)) != 0: + raise self.I2CWriteError("slave 0x{:X} write failed".format(self.__slaveaddr)) + + def readByte(self, addr, byteorder="big", signed=False): + return Integer.fromBytes(self.read(b'' if addr is None else bytes([addr]), 1), byteorder=byteorder, signed=signed) + + def writeByte(self, addr, value): + return self.write(b'' if addr is None else bytes([addr]), bytes([value])) + + def readWord(self, addr, byteorder="big", signed=False): + return Integer.fromBytes(self.read(b'' if addr is None else bytes([addr]), 2), byteorder=byteorder, signed=signed) + + def writeWord(self, addr, value, byteorder="big"): + return self.write(b'' if addr is None else bytes([addr]), Integer(value).toBytes(2, byteorder=byteorder)) diff --git a/BG93-M3-Tracker/code/libs/logging.py b/BG93-M3-Tracker/code/libs/logging.py new file mode 100644 index 0000000..d43806d --- /dev/null +++ b/BG93-M3-Tracker/code/libs/logging.py @@ -0,0 +1,131 @@ +import utime +import sys +import uio as io +import _thread + + +class Level(object): + DEBUG = 0 + INFO = 1 + WARN = 2 + ERROR = 3 + CRITICAL = 4 + + +_levelToName = { + Level.CRITICAL: 'CRITICAL', + Level.ERROR: 'ERROR', + Level.WARN: 'WARN', + Level.INFO: 'INFO', + Level.DEBUG: 'DEBUG' +} + +_nameToLevel = { + 'CRITICAL': Level.CRITICAL, + 'ERROR': Level.ERROR, + 'WARN': Level.WARN, + 'INFO': Level.INFO, + 'DEBUG': Level.DEBUG, +} + + +def getLevelName(level): + if level not in _levelToName: + raise ValueError('unknown level \"{}\", choose from .'.format(level)) + return _levelToName[level] + + +def getNameLevel(name): + temp = name.upper() + if temp not in _nameToLevel: + raise ValueError('\"{}\" is not valid. choose from [{}]'.format(name, list(_nameToLevel.keys()))) + return _nameToLevel[temp] + + +class BasicConfig(object): + logger_register_table = {} + basic_configure = { + 'level': Level.WARN, + 'debug': True, + 'stream': sys.stdout + } + + @classmethod + def getLogger(cls, name): + if name not in cls.logger_register_table: + logger = Logger(name) + cls.logger_register_table[name] = logger + else: + logger = cls.logger_register_table[name] + return logger + + @classmethod + def update(cls, **kwargs): + level = kwargs.pop('level', None) + if level is not None: + kwargs['level'] = getNameLevel(level) + return cls.basic_configure.update(kwargs) + + @classmethod + def get(cls, key): + return cls.basic_configure[key] + + @classmethod + def set(cls, key, value): + if key == 'level': + value = getNameLevel(value) + cls.basic_configure[key] = value + + +class Logger(object): + lock = _thread.allocate_lock() + + def __init__(self, name): + self.name = name + + @staticmethod + def __get_formatted_time(): + # (2023, 9, 30, 11, 11, 41, 5, 273) + cur_time_tuple = utime.localtime() + return '{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format( + cur_time_tuple[0], + cur_time_tuple[1], + cur_time_tuple[2], + cur_time_tuple[3], + cur_time_tuple[4], + cur_time_tuple[5] + ) + + def log(self, level, *message): + if not BasicConfig.get('debug'): + if level < BasicConfig.get('level'): + return + stream = BasicConfig.get('stream') + prefix = '[{}][{}][{}]'.format( + self.__get_formatted_time(), + getLevelName(level), + self.name, + ) + with self.lock: + print(prefix, *message, file=stream) + if isinstance(stream, io.TextIOWrapper): + stream.flush() + + def debug(self, *message): + self.log(Level.DEBUG, *message) + + def info(self, *message): + self.log(Level.INFO, *message) + + def warn(self, *message): + self.log(Level.WARN, *message) + + def error(self, *message): + self.log(Level.ERROR, *message) + + def critical(self, *message): + self.log(Level.CRITICAL, *message) + + +def getLogger(name): + return BasicConfig.getLogger(name) diff --git a/BG93-M3-Tracker/code/libs/pypubsub.py b/BG93-M3-Tracker/code/libs/pypubsub.py new file mode 100644 index 0000000..d4585b5 --- /dev/null +++ b/BG93-M3-Tracker/code/libs/pypubsub.py @@ -0,0 +1,74 @@ +"""基于 QuecPython 的订阅/发布机制""" + + +from usr.libs.threading import Thread, Queue, Lock + + +class Publisher(object): + + def __init__(self): + self.__q = Queue() + self.__topic_manager_lock = Lock() + self.__topic_manager = {} + self.__listen_thread = Thread(target=self.__listen_worker) + + def listen(self): + self.__listen_thread.start() + + def __listen_worker(self): + while True: + topic, messages = self.__q.get() + # print("topic: {}, messages: {}".format(topic, messages)) + with self.__topic_manager_lock: + for listener in self.__topic_manager.setdefault(topic, []): + try: + listener(**messages) + except Exception as e: + print("listener error:", str(e)) + + def publish(self, topic, **kwargs): + self.__q.put((topic, kwargs)) + + def subscribe(self, topic, listener): + with self.__topic_manager_lock: + listener_list = self.__topic_manager.setdefault(topic, []) + listener_list.append(listener) + + def unsubscribe(self, topic, listener): + with self.__topic_manager_lock: + listener_list = self.__topic_manager.setdefault(topic, []) + try: + listener_list.remove(listener) + except ValueError: + pass + + +# global publisher +__publisher__ = None + + +def get_default_publisher(): + global __publisher__ + if __publisher__ is None: + __publisher__ = Publisher() + __publisher__.listen() + return __publisher__ + + + +def publish(topic, **kwargs): + """订阅消息""" + pub = get_default_publisher() + pub.publish(topic, **kwargs) + + +def subscribe(topic, listener): + """订阅消息""" + pub = get_default_publisher() + pub.subscribe(topic, listener) + + +def unsubscribe(topic, listener): + """取消订阅消息""" + pub = get_default_publisher() + pub.unsubscribe(topic, listener) diff --git a/BG93-M3-Tracker/code/libs/threading.py b/BG93-M3-Tracker/code/libs/threading.py new file mode 100644 index 0000000..5a8a327 --- /dev/null +++ b/BG93-M3-Tracker/code/libs/threading.py @@ -0,0 +1,569 @@ +import utime +import sys +import _thread +import osTimer + + +class Lock(object): + + def __init__(self): + self.__lock = _thread.allocate_lock() + self.__owner = None + + def __enter__(self): + self.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.release() + + def acquire(self): + flag = self.__lock.acquire() + self.__owner = _thread.get_ident() + return flag + + def release(self): + self.__owner = None + return self.__lock.release() + + def locked(self): + return self.__lock.locked() + + @property + def owner(self): + return self.__owner + + +class _Waiter(object): + """WARNING: Waiter object can only be used once.""" + + def __init__(self): + self.__lock = Lock() + self.__lock.acquire() + self.__gotit = True + + @property + def __unlock_timer(self): + timer = getattr(self, '__unlock_timer__', None) + if timer is None: + timer = osTimer() + setattr(self, '__unlock_timer__', timer) + return timer + + @property + def __timer_lock(self): + lock = getattr(self, '__timer_lock__', None) + if lock is None: + lock = Lock() + setattr(self, '__timer_lock__', lock) + return lock + + def __auto_release(self, _): + with self.__timer_lock: + self.__gotit = not self.__release() + + def acquire(self, timeout=None): + if timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + gotit = self.__gotit + if timeout: + with self.__timer_lock: + self.__unlock_timer.start(timeout * 1000, 0, self.__auto_release) + self.__lock.acquire() # block here + if timeout: + with self.__timer_lock: + gotit = self.__gotit + self.__unlock_timer.stop() + return gotit + + def __release(self): + try: + self.__lock.release() + except RuntimeError: + return False + return True + + def release(self): + return self.__release() + + +class Condition(object): + + def __init__(self, lock=None): + if lock is None: + lock = Lock() + self.__lock = lock + self.__waiters = [] + self.acquire = self.__lock.acquire + self.release = self.__lock.release + + def __enter__(self): + self.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.release() + + def __is_owned(self): + return self.__lock.locked() and self.__lock.owner == _thread.get_ident() + + def wait(self, timeout=None): + if not self.__is_owned(): + raise RuntimeError('cannot wait on un-acquired lock.') + waiter = _Waiter() + self.__waiters.append(waiter) + self.release() + gotit = False + try: + gotit = waiter.acquire(timeout) + return gotit + finally: + self.acquire() + if not gotit: + try: + self.__waiters.remove(waiter) + except ValueError: + pass + + def wait_for(self, predicate, timeout=None): + endtime = None + remaining = timeout + result = predicate() + while not result: + if remaining is not None: + if endtime is None: + endtime = utime.time() + remaining + else: + remaining = endtime - utime.time() + if remaining <= 0.0: + break + self.wait(remaining) + result = predicate() + return result + + def notify(self, n=1): + if not self.__is_owned(): + raise RuntimeError('cannot wait on un-acquired lock.') + if n < 0: + raise ValueError('invalid param, n should be >= 0.') + waiters_to_notify = self.__waiters[:n] + for waiter in waiters_to_notify: + waiter.release() + try: + self.__waiters.remove(waiter) + except ValueError: + pass + + def notify_all(self): + self.notify(n=len(self.__waiters)) + + +class Event(object): + + def __init__(self): + self.__flag = False + self.__cond = Condition() + + def wait(self, timeout=None, clear=False): + with self.__cond: + result = self.__cond.wait_for(lambda: self.__flag, timeout=timeout) + if result and clear: + self.__flag = False + return result + + def set(self): + with self.__cond: + self.__flag = True + self.__cond.notify_all() + + def clear(self): + with self.__cond: + self.__flag = False + + def is_set(self): + with self.__cond: + return self.__flag + + +class EventSet(object): + + def __init__(self): + self.__set = 0 + self.__cond = Condition() + + def wait(self, event_set, timeout=None, clear=False): + with self.__cond: + result = self.__cond.wait_for(lambda: (event_set & self.__set) == event_set, timeout=timeout) + if result and clear: + self.__set &= ~event_set + return result + + def wait_any(self, event_set, timeout=None, clear=False): + with self.__cond: + result = self.__cond.wait_for(lambda: event_set & self.__set, timeout=timeout) + if result and clear: + self.__set &= ~event_set + return result + + def set(self, event_set): + with self.__cond: + self.__set |= event_set + self.__cond.notify_all() + + def clear(self, event_set): + with self.__cond: + self.__set &= ~event_set + + def is_set(self, event_set): + with self.__cond: + return (self.__set & event_set) == event_set + + def is_set_any(self, event_set): + with self.__cond: + return self.__set & event_set + + +class Semaphore(object): + + def __init__(self, value=1): + if value < 0: + raise ValueError("semaphore initial value must be >= 0") + self.__value = value + self.__cond = Condition() + + def __enter__(self): + self.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.release() + + def counts(self): + with self.__cond: + return self.__value + + def acquire(self, block=True, timeout=None): + with self.__cond: + if not block: + if self.__value > 0: + self.__value -= 1 + return True + else: + return False + elif timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + else: + if self.__cond.wait_for(lambda: self.__value > 0, timeout=timeout): + self.__value -= 1 + return True + else: + return False + + def release(self, n=1): + if n < 1: + raise ValueError('n must be one or more') + with self.__cond: + self.__value += n + self.__cond.notify(n) + + def clear(self): + with self.__cond: + self.__value = 0 + + +class BoundedSemaphore(Semaphore): + + def __init__(self, value=1): + super().__init__(value) + self.__initial_value = value + + def release(self, n=1): + if n < 1: + raise ValueError('n must be one or more') + with self.__cond: + if self.__value + n > self.__initial_value: + raise ValueError("Semaphore released too many times") + self.__value += n + self.__cond.notify(n) + + +class Queue(object): + class Full(Exception): + pass + + class Empty(Exception): + pass + + def __init__(self, max_size=100): + self.queue = [] + self.__max_size = max_size + self.__lock = Lock() + self.__not_empty = Condition(self.__lock) + self.__not_full = Condition(self.__lock) + + def _put(self, item): + self.queue.append(item) + + def put(self, item, block=True, timeout=None): + with self.__not_full: + if not block: + if len(self.queue) >= self.__max_size: + raise self.Full + elif timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + else: + if not self.__not_full.wait_for(lambda: len(self.queue) < self.__max_size, timeout=timeout): + raise self.Full + self._put(item) + self.__not_empty.notify() + + def _get(self): + return self.queue.pop(0) + + def get(self, block=True, timeout=None): + with self.__not_empty: + if not block: + if len(self.queue) == 0: + raise self.Empty + elif timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + else: + if not self.__not_empty.wait_for(lambda: len(self.queue) != 0, timeout=timeout): + raise self.Empty + item = self._get() + self.__not_full.notify() + return item + + def size(self): + with self.__lock: + return len(self.queue) + + def clear(self): + with self.__lock: + self.queue.clear() + + +class LifoQueue(Queue): + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() + + +class PriorityQueue(Queue): + + @classmethod + def __siftdown(cls, heap, startpos, pos): + newitem = heap[pos] + while pos > startpos: + parentpos = (pos - 1) >> 1 + parent = heap[parentpos] + if newitem < parent: + heap[pos] = parent + pos = parentpos + continue + break + heap[pos] = newitem + + def _put(self, item): + self.queue.append(item) + self.__siftdown(self.queue, 0, len(self.queue) - 1) + + @classmethod + def __siftup(cls, heap, pos): + endpos = len(heap) + startpos = pos + newitem = heap[pos] + childpos = 2 * pos + 1 + while childpos < endpos: + rightpos = childpos + 1 + if rightpos < endpos and not heap[childpos] < heap[rightpos]: + childpos = rightpos + heap[pos] = heap[childpos] + pos = childpos + childpos = 2 * pos + 1 + heap[pos] = newitem + cls.__siftdown(heap, startpos, pos) + + def _get(self): + lastelt = self.queue.pop() + if self.queue: + returnitem = self.queue[0] + self.queue[0] = lastelt + self.__siftup(self.queue, 0) + return returnitem + return lastelt + + +class Thread(object): + DEFAULT_STACK_SIZE = _thread.stack_size() + + def __init__(self, target=None, args=(), kwargs=None): + self.__target = target + self.__args = args + self.__kwargs = kwargs or {} + self.__ident = None + self.__stopped_event = Event() + + def is_running(self): + if self.__ident is None: + return False + else: + return _thread.threadIsRunning(self.__ident) + + def join(self, timeout=None): + return self.__stopped_event.wait(timeout=timeout) + + def terminate(self): + """WARNING: you must release all resources after terminate thread, especially **Lock(s)**""" + if self.is_running(): + _thread.stop_thread(self.ident) + self.__ident = None + self.__stopped_event.set() + + def start(self, stack_size=None): + if self.__ident is not None: + raise RuntimeError("threads can only be started once") + if stack_size is not None: + _thread.stack_size(stack_size * 1024) + self.__ident = _thread.start_new_thread(self.__bootstrap, ()) + if stack_size is not None: + _thread.stack_size(self.DEFAULT_STACK_SIZE) + + def __bootstrap(self): + try: + self.run() + except Exception as e: + sys.print_exception(e) + finally: + self.__stopped_event.set() + + def run(self): + if self.__target: + self.__target(*self.__args, **self.__kwargs) + + @property + def ident(self): + return self.__ident + + +class _Result(object): + class TimeoutError(Exception): + pass + + class NotReadyError(Exception): + pass + + def __init__(self): + self.__rv = None + self.__exc = None + self.__finished = Event() + + def set(self, exc=None, rv=None): + self.__exc = exc + self.__rv = rv + self.__finished.set() + + def __get_value_or_raise_exc(self): + if self.__exc: + raise self.__exc + return self.__rv + + def get(self, block=True, timeout=None): + if not block: + if self.__finished.is_set(): + return self.__get_value_or_raise_exc() + raise self.NotReadyError('result not ready') + if self.__finished.wait(timeout=timeout): + return self.__get_value_or_raise_exc() + else: + raise self.TimeoutError('get result timeout.') + + +class AsyncTask(object): + + def __init__(self, target=None, args=(), kwargs=None): + self.__target = target + self.__args = args + self.__kwargs = kwargs or {} + + def delay(self, seconds=None): + result = _Result() + Thread(target=self.__run, args=(result, seconds)).start() + return result + + def __run(self, result, delay_seconds): + if delay_seconds is not None and delay_seconds > 0: + utime.sleep(delay_seconds) + try: + rv = self.__target(*self.__args, **self.__kwargs) + except Exception as e: + sys.print_exception(e) + result.set(exc=e) + else: + result.set(rv=rv) + + @classmethod + def wrapper(cls, func): + def inner_wrapper(*args, **kwargs): + return cls(target=func, args=args, kwargs=kwargs) + return inner_wrapper + + +class _WorkItem(object): + + def __init__(self, target=None, args=(), kwargs=None): + self.__target = target + self.__args = args + self.__kwargs = kwargs or {} + self.result = _Result() + + def __call__(self, *args, **kwargs): + try: + rv = self.__target(*self.__args, **self.__kwargs) + except Exception as e: + self.result.set(exc=e) + else: + self.result.set(rv=rv) + + +def _worker(work_queue): + while True: + try: + task = work_queue.get() + task() + except Exception as e: + sys.print_exception(e) + + +class ThreadPoolExecutor(object): + + def __init__(self, max_workers=4): + if max_workers <= 0: + raise ValueError('max_workers must be greater than 0.') + self.__max_workers = max_workers + self.__work_queue = Queue() + self.__threads = set() + self.__lock = Lock() + + def submit(self, *args, **kwargs): + with self.__lock: + item = _WorkItem(*args, **kwargs) + self.__work_queue.put(item) + self.__adjust_thread_count() + return item.result + + def __adjust_thread_count(self): + if len(self.__threads) < self.__max_workers: + t = Thread(target=_worker, args=(self.__work_queue,)) + t.start() + self.__threads.add(t) + + def shutdown(self): + with self.__lock: + for t in self.__threads: + t.terminate() + self.__threads = set() + self.__work_queue = Queue() diff --git a/BG93-M3-Tracker/code/main.py b/BG93-M3-Tracker/code/main.py new file mode 100644 index 0000000..088f361 --- /dev/null +++ b/BG93-M3-Tracker/code/main.py @@ -0,0 +1,75 @@ +import utime +import dataCall +from umqtt import MQTTClient +try: + from libs.logging import getLogger + from libs import Application + from extensions import ( + qth_client, + # gnss_service, + lbs_service, + sensor_service, +) +except ImportError: + from usr.libs.logging import getLogger + from usr.libs import Application + from usr.extensions import ( + qth_client, + # gnss_service, + lbs_service, + sensor_service, + ) + +WAIT_NETWORK_READY_S = 30 # 30s + +logger = getLogger(__name__) + + +def create_app(name="SimpliKit", version="1.0.0", config_path="/usr/config.json"): + _app = Application(name, version) + _app.config.init(config_path) + + qth_client.init_app(_app) + # gnss_service.init_app(_app) + lbs_service.init_app(_app) + sensor_service.init_app(_app) + + return _app + + +def wait_network_ready(): + wait_cnt = WAIT_NETWORK_READY_S / 5 + is_ready = False + + while wait_cnt: + lte = dataCall.getInfo(1, 0) + if lte[2][0] == 1: + is_ready = True + break + + utime.sleep(5) + wait_cnt -= 1 + + return is_ready + + +if __name__ == "__main__": + while True: + if wait_network_ready(): + logger.debug('lte network normal') + break + + logger.debug('wait lte network normal...') + ret=dataCall.setPDPContext(1, 0, 'BICSAPN', '', '', 0) + ret2=dataCall.activate(1) + while not ret and ret2: + ret=dataCall.setPDPContext(1, 0, 'BICSAPN', '', '', 0) # 激活之前,应该先配置APN,这里配置第1路的APN + ret2=dataCall.activate(1) + if ret and ret2: + print("Net injection failure") + break + + app = create_app() + app.run() + + diff --git a/BG93-M3-Tracker/firmware/QPY_OCPU_V0003_BG95M3_FW.bin b/BG93-M3-Tracker/firmware/QPY_OCPU_V0003_BG95M3_FW.bin new file mode 100644 index 0000000..75abbf2 Binary files /dev/null and b/BG93-M3-Tracker/firmware/QPY_OCPU_V0003_BG95M3_FW.bin differ diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6fac56c..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,42 +0,0 @@ -# ChangeLog - -All significant changes to this project will be documented in this file. - -## [v2.0.0] - 2024-06-04 - -### Added - -- Restructured the entire Tracker's business functions, adding three new modules: `tracker_collector` (collector), `tracker_controller` (controller), and `tracker_devicecheck` (device status checker). -- Extracted non-business functionalities into separate, independent modules housed within the standalone `modules` project. -- Included sample JSON files for Aliyun and QuecCloud IoT device models. -- **net_manage.py**: Introduced a network management module; -- **power_manage.py**: Added a low-power management module, replacing the previous `mpower.py`; -- **thingsboard.py**: Incorporated MQTT client code for connecting to the ThingsBoard platform; - -### Changed - -- Refined the overall project architecture, dedicating individual modules to specific platform functionalities (e.g., `tracker_ali.py` for Aliyun, `tracker_tb.py` for ThingsBoard). -- Optimized code within several modules, including: - + **aliyunIot.py** - + **battery.py** - + **buzzer.py** - + **common.py** - + **led.py** - + **location.py** - + **logging.py** - -### Removed - -- Removed the ota module as OTA upgrade capabilities are now integrated into respective cloud functionality modules. -- Discontinued the timer module; LED blinking functionality previously in the timer module has been moved to the LED module. -- Decommissioned the Tracker class in the tracker module, redistributing its functionalities among `tracker_collector`, `tracker_controller`, and `tracker_devicecheck`. -- Refactored the SelfCheck function into `tracker_devicecheck`. -- Migrated Aliyun, QuecCloud IoT, battery, common, history, LED, location, logging, and cloud interaction middleware modules into the `modules` directory. -- Retired the QuecCloud module. -- Eliminated the `remote` middleware module. - -## [v1.0.0] - 2024-06-04 - -### Initial Release - -- Initial release for this project. \ No newline at end of file diff --git a/EG912U-GL-Tracker/LICENSE b/EG912U-GL-Tracker/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/EG912U-GL-Tracker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/EG912U-GL-Tracker/README.md b/EG912U-GL-Tracker/README.md new file mode 100644 index 0000000..9345414 --- /dev/null +++ b/EG912U-GL-Tracker/README.md @@ -0,0 +1 @@ +# tracker_EG912U \ No newline at end of file diff --git a/EG912U-GL-Tracker/code/Qth/__init__.mpy b/EG912U-GL-Tracker/code/Qth/__init__.mpy new file mode 100644 index 0000000..05fa4b8 Binary files /dev/null and b/EG912U-GL-Tracker/code/Qth/__init__.mpy differ diff --git a/EG912U-GL-Tracker/code/Qth/qth_bus.mpy b/EG912U-GL-Tracker/code/Qth/qth_bus.mpy new file mode 100644 index 0000000..a9b8389 Binary files /dev/null and b/EG912U-GL-Tracker/code/Qth/qth_bus.mpy differ diff --git a/EG912U-GL-Tracker/code/Qth/qth_config.mpy b/EG912U-GL-Tracker/code/Qth/qth_config.mpy new file mode 100644 index 0000000..88685f2 Binary files /dev/null and b/EG912U-GL-Tracker/code/Qth/qth_config.mpy differ diff --git a/EG912U-GL-Tracker/code/Qth/qth_dmp.mpy b/EG912U-GL-Tracker/code/Qth/qth_dmp.mpy new file mode 100644 index 0000000..f2e9f6e Binary files /dev/null and b/EG912U-GL-Tracker/code/Qth/qth_dmp.mpy differ diff --git a/EG912U-GL-Tracker/code/Qth/qth_file.mpy b/EG912U-GL-Tracker/code/Qth/qth_file.mpy new file mode 100644 index 0000000..dfc2f3c Binary files /dev/null and b/EG912U-GL-Tracker/code/Qth/qth_file.mpy differ diff --git a/EG912U-GL-Tracker/code/Qth/qth_ota.mpy b/EG912U-GL-Tracker/code/Qth/qth_ota.mpy new file mode 100644 index 0000000..18eaa76 Binary files /dev/null and b/EG912U-GL-Tracker/code/Qth/qth_ota.mpy differ diff --git a/EG912U-GL-Tracker/code/Qth/qth_protocal.mpy b/EG912U-GL-Tracker/code/Qth/qth_protocal.mpy new file mode 100644 index 0000000..ef8d78e Binary files /dev/null and b/EG912U-GL-Tracker/code/Qth/qth_protocal.mpy differ diff --git "a/EG912U-GL-Tracker/code/Qth/\344\276\235\350\265\226quecpython module\346\270\205\345\215\225.md" "b/EG912U-GL-Tracker/code/Qth/\344\276\235\350\265\226quecpython module\346\270\205\345\215\225.md" new file mode 100644 index 0000000..4ac7bec --- /dev/null +++ "b/EG912U-GL-Tracker/code/Qth/\344\276\235\350\265\226quecpython module\346\270\205\345\215\225.md" @@ -0,0 +1,22 @@ +| module | 描述| +| --- | --- | +|uzlib|zlib解压缩| +|utime|时间相关功能| +|uos|基本系统服务| +|ujson|JSON编码和解码| +|uhashlib|哈希算法| +|urandom|生成随机数| +|ubinascii|二进制与ASCLL转换| +|umqtt|MQTT客户端| +|_thread|多线程| +|net|网络相关功能| +|sim|SIM卡功能| +|misc.Power|关机以及软件重启功能| +|ql_fs|高级文件操作| +|fota|fota升级| +|app_fota_download|py脚本升级| +|log|日志输出| +|modem|设备相关| +|queue|消息队列| +|request|http客户端| + diff --git a/EG912U-GL-Tracker/code/config.json b/EG912U-GL-Tracker/code/config.json new file mode 100644 index 0000000..3af9d81 --- /dev/null +++ b/EG912U-GL-Tracker/code/config.json @@ -0,0 +1,5 @@ +{ + "QTH_PRODUCT_KEY": "pe16Db", + "QTH_PRODUCT_SECRET": "ZGZMQWQ3QkVyN2Jm", + "QTH_SERVER": "mqtt://iot-south.acceleronix.io:1883" +} \ No newline at end of file diff --git a/EG912U-GL-Tracker/code/config2.json b/EG912U-GL-Tracker/code/config2.json new file mode 100644 index 0000000..e5aa3b8 --- /dev/null +++ b/EG912U-GL-Tracker/code/config2.json @@ -0,0 +1,5 @@ +{ + "QTH_PRODUCT_KEY": "pe17gQ", + "QTH_PRODUCT_SECRET": "REdmNmlMRS8yUmNi", + "QTH_SERVER": "mqtt://iot-south.quectelcn.com:1883" +} \ No newline at end of file diff --git a/EG912U-GL-Tracker/code/drivers/icm20948.py b/EG912U-GL-Tracker/code/drivers/icm20948.py new file mode 100644 index 0000000..cfbf9ae --- /dev/null +++ b/EG912U-GL-Tracker/code/drivers/icm20948.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- +import time +import math +from usr.libs.i2c import I2CIOWrapper + +Gyro = [0,0,0] +Accel = [0,0,0] +Mag = [0,0,0] +pitch = 0.0 +roll = 0.0 +yaw = 0.0 +pu8data=[0,0,0,0,0,0,0,0] +U8tempX=[0,0,0,0,0,0,0,0,0] +U8tempY=[0,0,0,0,0,0,0,0,0] +U8tempZ=[0,0,0,0,0,0,0,0,0] +GyroOffset=[0,0,0] +Ki = 1.0 +Kp = 4.50 +q0 = 1.0 +q1=q2=q3=0.0 +angles=[0.0,0.0,0.0] +true =0x01 +false =0x00 +# define ICM-20948 Device I2C address +I2C_ADD_ICM20948 = 0x68 +I2C_ADD_ICM20948_AK09916 = 0x0C +I2C_ADD_ICM20948_AK09916_READ = 0x80 +I2C_ADD_ICM20948_AK09916_WRITE = 0x00 +# define ICM-20948 Register +# user bank 0 register +REG_ADD_WIA = 0x00 +REG_VAL_WIA = 0xEA +REG_ADD_USER_CTRL = 0x03 +REG_VAL_BIT_DMP_EN = 0x80 +REG_VAL_BIT_FIFO_EN = 0x40 +REG_VAL_BIT_I2C_MST_EN = 0x20 +REG_VAL_BIT_I2C_IF_DIS = 0x10 +REG_VAL_BIT_DMP_RST = 0x08 +REG_VAL_BIT_DIAMOND_DMP_RST = 0x04 +REG_ADD_PWR_MIGMT_1 = 0x06 +REG_VAL_ALL_RGE_RESET = 0x80 +REG_VAL_RUN_MODE = 0x01 # Non low-power mode +REG_ADD_LP_CONFIG = 0x05 +REG_ADD_PWR_MGMT_1 = 0x06 +REG_ADD_PWR_MGMT_2 = 0x07 +REG_ADD_ACCEL_XOUT_H = 0x2D +REG_ADD_ACCEL_XOUT_L = 0x2E +REG_ADD_ACCEL_YOUT_H = 0x2F +REG_ADD_ACCEL_YOUT_L = 0x30 +REG_ADD_ACCEL_ZOUT_H = 0x31 +REG_ADD_ACCEL_ZOUT_L = 0x32 +REG_ADD_GYRO_XOUT_H = 0x33 +REG_ADD_GYRO_XOUT_L = 0x34 +REG_ADD_GYRO_YOUT_H = 0x35 +REG_ADD_GYRO_YOUT_L = 0x36 +REG_ADD_GYRO_ZOUT_H = 0x37 +REG_ADD_GYRO_ZOUT_L = 0x38 +REG_ADD_EXT_SENS_DATA_00 = 0x3B +REG_ADD_REG_BANK_SEL = 0x7F +REG_VAL_REG_BANK_0 = 0x00 +REG_VAL_REG_BANK_1 = 0x10 +REG_VAL_REG_BANK_2 = 0x20 +REG_VAL_REG_BANK_3 = 0x30 + +# user bank 1 register +# user bank 2 register +REG_ADD_GYRO_SMPLRT_DIV = 0x00 +REG_ADD_GYRO_CONFIG_1 = 0x01 +REG_VAL_BIT_GYRO_DLPCFG_2 = 0x10 # bit[5:3] +REG_VAL_BIT_GYRO_DLPCFG_4 = 0x20 # bit[5:3] +REG_VAL_BIT_GYRO_DLPCFG_6 = 0x30 # bit[5:3] +REG_VAL_BIT_GYRO_FS_250DPS = 0x00 # bit[2:1] +REG_VAL_BIT_GYRO_FS_500DPS = 0x02 # bit[2:1] +REG_VAL_BIT_GYRO_FS_1000DPS = 0x04 # bit[2:1] +REG_VAL_BIT_GYRO_FS_2000DPS = 0x06 # bit[2:1] +REG_VAL_BIT_GYRO_DLPF = 0x01 # bit[0] +REG_ADD_ACCEL_SMPLRT_DIV_2 = 0x11 +REG_ADD_ACCEL_CONFIG = 0x14 +REG_VAL_BIT_ACCEL_DLPCFG_2 = 0x10 # bit[5:3] +REG_VAL_BIT_ACCEL_DLPCFG_4 = 0x20 # bit[5:3] +REG_VAL_BIT_ACCEL_DLPCFG_6 = 0x30 # bit[5:3] +REG_VAL_BIT_ACCEL_FS_2g = 0x00 # bit[2:1] +REG_VAL_BIT_ACCEL_FS_4g = 0x02 # bit[2:1] +REG_VAL_BIT_ACCEL_FS_8g = 0x04 # bit[2:1] +REG_VAL_BIT_ACCEL_FS_16g = 0x06 # bit[2:1] +REG_VAL_BIT_ACCEL_DLPF = 0x01 # bit[0] + +# user bank 3 register +REG_ADD_I2C_SLV0_ADDR = 0x03 +REG_ADD_I2C_SLV0_REG = 0x04 +REG_ADD_I2C_SLV0_CTRL = 0x05 +REG_VAL_BIT_SLV0_EN = 0x80 +REG_VAL_BIT_MASK_LEN = 0x07 +REG_ADD_I2C_SLV0_DO = 0x06 +REG_ADD_I2C_SLV1_ADDR = 0x07 +REG_ADD_I2C_SLV1_REG = 0x08 +REG_ADD_I2C_SLV1_CTRL = 0x09 +REG_ADD_I2C_SLV1_DO = 0x0A + +# define ICM-20948 Register end + +# define ICM-20948 MAG Register +REG_ADD_MAG_WIA1 = 0x00 +REG_VAL_MAG_WIA1 = 0x48 +REG_ADD_MAG_WIA2 = 0x01 +REG_VAL_MAG_WIA2 = 0x09 +REG_ADD_MAG_ST2 = 0x10 +REG_ADD_MAG_DATA = 0x11 +REG_ADD_MAG_CNTL2 = 0x31 +REG_VAL_MAG_MODE_PD = 0x00 +REG_VAL_MAG_MODE_SM = 0x01 +REG_VAL_MAG_MODE_10HZ = 0x02 +REG_VAL_MAG_MODE_20HZ = 0x04 +REG_VAL_MAG_MODE_50HZ = 0x05 +REG_VAL_MAG_MODE_100HZ = 0x08 +REG_VAL_MAG_MODE_ST = 0x10 +# define ICM-20948 MAG Register end + +MAG_DATA_LEN =6 + +class ICM20948(I2CIOWrapper): + def __init__(self, i2c, address=I2C_ADD_ICM20948): + super().__init__(i2c, address) + + + bRet=self.icm20948Check() #Initialization of the device multiple times after power on will result in a return error + # while true != bRet: + # print("ICM-20948 Error\n" ) + # time.sleep(0.5) + # print("ICM-20948 OK\n" ) + time.sleep(0.5) #We can skip this detection by delaying it by 500 milliseconds + # user bank 0 register + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_0) + self._write_byte( REG_ADD_PWR_MIGMT_1 , REG_VAL_ALL_RGE_RESET) + time.sleep(0.1) + self._write_byte( REG_ADD_PWR_MIGMT_1 , REG_VAL_RUN_MODE) + #user bank 2 register + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_2) + self._write_byte( REG_ADD_GYRO_SMPLRT_DIV , 0x07) + self._write_byte( REG_ADD_GYRO_CONFIG_1 , REG_VAL_BIT_GYRO_DLPCFG_6 | REG_VAL_BIT_GYRO_FS_1000DPS | REG_VAL_BIT_GYRO_DLPF) + self._write_byte( REG_ADD_ACCEL_SMPLRT_DIV_2 , 0x07) + self._write_byte( REG_ADD_ACCEL_CONFIG , REG_VAL_BIT_ACCEL_DLPCFG_6 | REG_VAL_BIT_ACCEL_FS_2g | REG_VAL_BIT_ACCEL_DLPF) + #user bank 0 register + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_0) + time.sleep(0.1) + self.icm20948GyroOffset() + self.icm20948MagCheck() + self.icm20948WriteSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_WRITE,REG_ADD_MAG_CNTL2, REG_VAL_MAG_MODE_20HZ) + + def icm20948_Gyro_Accel_Read(self): + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_0) + data =self._read_block(REG_ADD_ACCEL_XOUT_H, 12) + self._write_byte( REG_ADD_REG_BANK_SEL , REG_VAL_REG_BANK_2) + Accel[0] = (data[0]<<8)|data[1] + Accel[1] = (data[2]<<8)|data[3] + Accel[2] = (data[4]<<8)|data[5] + Gyro[0] = ((data[6]<<8)|data[7]) - GyroOffset[0] + Gyro[1] = ((data[8]<<8)|data[9]) - GyroOffset[1] + Gyro[2] = ((data[10]<<8)|data[11]) - GyroOffset[2] + if Accel[0]>=32767: #Solve the problem that Python shift will not overflow + Accel[0]=Accel[0]-65535 + elif Accel[0]<=-32767: + Accel[0]=Accel[0]+65535 + if Accel[1]>=32767: + Accel[1]=Accel[1]-65535 + elif Accel[1]<=-32767: + Accel[1]=Accel[1]+65535 + if Accel[2]>=32767: + Accel[2]=Accel[2]-65535 + elif Accel[2]<=-32767: + Accel[2]=Accel[2]+65535 + if Gyro[0]>=32767: + Gyro[0]=Gyro[0]-65535 + elif Gyro[0]<=-32767: + Gyro[0]=Gyro[0]+65535 + if Gyro[1]>=32767: + Gyro[1]=Gyro[1]-65535 + elif Gyro[1]<=-32767: + Gyro[1]=Gyro[1]+65535 + if Gyro[2]>=32767: + Gyro[2]=Gyro[2]-65535 + elif Gyro[2]<=-32767: + Gyro[2]=Gyro[2]+65535 + def icm20948MagRead(self): + counter=20 + while(counter>0): + time.sleep(0.01) + self.icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ , REG_ADD_MAG_ST2, 1) + if ((pu8data[0] & 0x01)!= 0): + break + counter-=1 + if counter!=0: + for i in range(0,8): + self.icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ , REG_ADD_MAG_DATA , MAG_DATA_LEN) + U8tempX[i] = (pu8data[1]<<8)|pu8data[0] + U8tempY[i] = (pu8data[3]<<8)|pu8data[2] + U8tempZ[i] = (pu8data[5]<<8)|pu8data[4] + Mag[0]=(U8tempX[0]+U8tempX[1]+U8tempX[2]+U8tempX[3]+U8tempX[4]+U8tempX[5]+U8tempX[6]+U8tempX[7])/8 + Mag[1]=-(U8tempY[0]+U8tempY[1]+U8tempY[2]+U8tempY[3]+U8tempY[4]+U8tempY[5]+U8tempY[6]+U8tempY[7])/8 + Mag[2]=-(U8tempZ[0]+U8tempZ[1]+U8tempZ[2]+U8tempZ[3]+U8tempZ[4]+U8tempZ[5]+U8tempZ[6]+U8tempZ[7])/8 + if Mag[0]>=32767: #Solve the problem that Python shift will not overflow + Mag[0]=Mag[0]-65535 + elif Mag[0]<=-32767: + Mag[0]=Mag[0]+65535 + if Mag[1]>=32767: + Mag[1]=Mag[1]-65535 + elif Mag[1]<=-32767: + Mag[1]=Mag[1]+65535 + if Mag[2]>=32767: + Mag[2]=Mag[2]-65535 + elif Mag[2]<=-32767: + Mag[2]=Mag[2]+65535 + def icm20948ReadSecondary(self,u8I2CAddr,u8RegAddr,u8Len): + u8Temp=0 + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + self._write_byte( REG_ADD_I2C_SLV0_ADDR, u8I2CAddr) + self._write_byte( REG_ADD_I2C_SLV0_REG, u8RegAddr) + self._write_byte( REG_ADD_I2C_SLV0_CTRL, REG_VAL_BIT_SLV0_EN|u8Len) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + + u8Temp = self._read_byte(REG_ADD_USER_CTRL) + u8Temp |= REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + time.sleep(0.01) + u8Temp &= ~REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + + for i in range(0,u8Len): + pu8data[i]= self._read_byte( REG_ADD_EXT_SENS_DATA_00+i) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + + u8Temp = self._read_byte(REG_ADD_I2C_SLV0_CTRL) + u8Temp &= ~((REG_VAL_BIT_I2C_MST_EN)&(REG_VAL_BIT_MASK_LEN)) + self._write_byte( REG_ADD_I2C_SLV0_CTRL, u8Temp) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + def icm20948WriteSecondary(self,u8I2CAddr,u8RegAddr,u8data): + u8Temp=0 + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + self._write_byte( REG_ADD_I2C_SLV1_ADDR, u8I2CAddr) + self._write_byte( REG_ADD_I2C_SLV1_REG, u8RegAddr) + self._write_byte( REG_ADD_I2C_SLV1_DO, u8data) + self._write_byte( REG_ADD_I2C_SLV1_CTRL, REG_VAL_BIT_SLV0_EN|1) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + + u8Temp = self._read_byte(REG_ADD_USER_CTRL) + u8Temp |= REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + time.sleep(0.01) + u8Temp &= ~REG_VAL_BIT_I2C_MST_EN + self._write_byte( REG_ADD_USER_CTRL, u8Temp) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3) #swtich bank3 + + u8Temp = self._read_byte(REG_ADD_I2C_SLV0_CTRL) + u8Temp &= ~((REG_VAL_BIT_I2C_MST_EN)&(REG_VAL_BIT_MASK_LEN)) + self._write_byte( REG_ADD_I2C_SLV0_CTRL, u8Temp) + + self._write_byte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0) #swtich bank0 + def icm20948GyroOffset(self): + s32TempGx = 0 + s32TempGy = 0 + s32TempGz = 0 + for i in range(0,32): + self.icm20948_Gyro_Accel_Read() + s32TempGx += Gyro[0] + s32TempGy += Gyro[1] + s32TempGz += Gyro[2] + time.sleep(0.01) + GyroOffset[0] = s32TempGx >> 5 + GyroOffset[1] = s32TempGy >> 5 + GyroOffset[2] = s32TempGz >> 5 + def _read_byte(self, cmd): + return self.read(bytes([cmd]))[0] + + def _read_block(self, reg, length=1): + return self.read(bytes([reg]), size=length) + + def _read_u16(self,cmd): + LSB = self.read(bytes([cmd])) + MSB = self.read(bytes([cmd + 1])) + return (MSB << 8) + LSB + + def _write_byte(self,cmd,val): + self.write(bytes([cmd]), bytes([val])) + time.sleep(0.0001) + + def imuAHRSupdate(self,gx, gy,gz,ax,ay,az,mx,my,mz): + norm=0.0 + hx = hy = hz = bx = bz = 0.0 + vx = vy = vz = wx = wy = wz = 0.0 + exInt = eyInt = ezInt = 0.0 + ex=ey=ez=0.0 + halfT = 0.024 + global q0 + global q1 + global q2 + global q3 + q0q0 = q0 * q0 + q0q1 = q0 * q1 + q0q2 = q0 * q2 + q0q3 = q0 * q3 + q1q1 = q1 * q1 + q1q2 = q1 * q2 + q1q3 = q1 * q3 + q2q2 = q2 * q2 + q2q3 = q2 * q3 + q3q3 = q3 * q3 + + norm = float(1/math.sqrt(ax * ax + ay * ay + az * az)) + ax = ax * norm + ay = ay * norm + az = az * norm + + norm = float(1/math.sqrt(mx * mx + my * my + mz * mz)) + mx = mx * norm + my = my * norm + mz = mz * norm + + # compute reference direction of flux + hx = 2 * mx * (0.5 - q2q2 - q3q3) + 2 * my * (q1q2 - q0q3) + 2 * mz * (q1q3 + q0q2) + hy = 2 * mx * (q1q2 + q0q3) + 2 * my * (0.5 - q1q1 - q3q3) + 2 * mz * (q2q3 - q0q1) + hz = 2 * mx * (q1q3 - q0q2) + 2 * my * (q2q3 + q0q1) + 2 * mz * (0.5 - q1q1 - q2q2) + bx = math.sqrt((hx * hx) + (hy * hy)) + bz = hz + + # estimated direction of gravity and flux (v and w) + vx = 2 * (q1q3 - q0q2) + vy = 2 * (q0q1 + q2q3) + vz = q0q0 - q1q1 - q2q2 + q3q3 + wx = 2 * bx * (0.5 - q2q2 - q3q3) + 2 * bz * (q1q3 - q0q2) + wy = 2 * bx * (q1q2 - q0q3) + 2 * bz * (q0q1 + q2q3) + wz = 2 * bx * (q0q2 + q1q3) + 2 * bz * (0.5 - q1q1 - q2q2) + + # error is sum of cross product between reference direction of fields and direction measured by sensors + ex = (ay * vz - az * vy) + (my * wz - mz * wy) + ey = (az * vx - ax * vz) + (mz * wx - mx * wz) + ez = (ax * vy - ay * vx) + (mx * wy - my * wx) + + if (ex != 0.0 and ey != 0.0 and ez != 0.0): + exInt = exInt + ex * Ki * halfT + eyInt = eyInt + ey * Ki * halfT + ezInt = ezInt + ez * Ki * halfT + + gx = gx + Kp * ex + exInt + gy = gy + Kp * ey + eyInt + gz = gz + Kp * ez + ezInt + + q0 = q0 + (-q1 * gx - q2 * gy - q3 * gz) * halfT + q1 = q1 + (q0 * gx + q2 * gz - q3 * gy) * halfT + q2 = q2 + (q0 * gy - q1 * gz + q3 * gx) * halfT + q3 = q3 + (q0 * gz + q1 * gy - q2 * gx) * halfT + + norm = float(1/math.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3)) + q0 = q0 * norm + q1 = q1 * norm + q2 = q2 * norm + q3 = q3 * norm + def icm20948Check(self): + bRet=false + if REG_VAL_WIA == self._read_byte(REG_ADD_WIA): + bRet = true + return bRet + + def icm20948MagCheck(self): + self.icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ,REG_ADD_MAG_WIA1, 2) + if (pu8data[0] == REG_VAL_MAG_WIA1) and ( pu8data[1] == REG_VAL_MAG_WIA2) : + bRet = true + return bRet + def icm20948CalAvgValue(self): + MotionVal[0]=Gyro[0]/32.8 + MotionVal[1]=Gyro[1]/32.8 + MotionVal[2]=Gyro[2]/32.8 + MotionVal[3]=Accel[0] + MotionVal[4]=Accel[1] + MotionVal[5]=Accel[2] + MotionVal[6]=Mag[0] + MotionVal[7]=Mag[1] + MotionVal[8]=Mag[2] + + +if __name__ == '__main__': + import time + from machine import I2C + + print("\nSense HAT Test Program ...\n") + MotionVal=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] + icm20948=ICM20948(I2C(I2C.I2C1, I2C.STANDARD_MODE)) + while True: + try: + icm20948.icm20948_Gyro_Accel_Read() + icm20948.icm20948MagRead() + icm20948.icm20948CalAvgValue() + time.sleep(1) + icm20948.imuAHRSupdate(MotionVal[0] * 0.0175, MotionVal[1] * 0.0175,MotionVal[2] * 0.0175, + MotionVal[3],MotionVal[4],MotionVal[5], + MotionVal[6], MotionVal[7], MotionVal[8]) + pitch = math.asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3 + roll = math.atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3 + yaw = math.atan2(-2 * q1 * q2 - 2 * q0 * q3, 2 * q2 * q2 + 2 * q3 * q3 - 1) * 57.3 + print("\r\n /-------------------------------------------------------------/ \r\n") + print('\r\n Roll = %.2f , Pitch = %.2f , Yaw = %.2f\r\n'%(roll,pitch,yaw)) + print('\r\nAcceleration: X = %d , Y = %d , Z = %d\r\n'%(Accel[0],Accel[1],Accel[2])) + print('\r\nGyroscope: X = %d , Y = %d , Z = %d\r\n'%(Gyro[0],Gyro[1],Gyro[2])) + print('\r\nMagnetic: X = %d , Y = %d , Z = %d'%((Mag[0]),Mag[1],Mag[2])) + except(KeyboardInterrupt): + print("\n") + break diff --git a/EG912U-GL-Tracker/code/drivers/lps22hb.py b/EG912U-GL-Tracker/code/drivers/lps22hb.py new file mode 100644 index 0000000..d69b0a3 --- /dev/null +++ b/EG912U-GL-Tracker/code/drivers/lps22hb.py @@ -0,0 +1,94 @@ +import utime +from usr.libs.i2c import I2CIOWrapper + + +#i2c address +LPS22HB_SLAVE_ADDRESS = 0x5C +# id +LPS22HB_CHIP_ID = 0xB1 + +#Register +LPS_INT_CFG = b"\x0B" # Interrupt register +LPS_THS_P_L = b"\x0C" # Pressure threshold registers +LPS_THS_P_H = b"\x0D" +LPS_WHO_AM_I = b"\x0F" # Who am I +LPS_CTRL_REG1 = b"\x10" # Control registers +LPS_CTRL_REG2 = b"\x11" +LPS_CTRL_REG3 = b"\x12" +LPS_FIFO_CTRL = b"\x14" # FIFO configuration register +LPS_REF_P_XL = b"\x15" # Reference pressure registers +LPS_REF_P_L = b"\x16" +LPS_REF_P_H = b"\x17" +LPS_RPDS_L = b"\x18" # Pressure offset registers +LPS_RPDS_H = b"\x19" +LPS_RES_CONF = b"\x1A" # Resolution register +LPS_INT_SOURCE = b"\x25" # Interrupt register +LPS_FIFO_STATUS = b"\x26" # FIFO status register +LPS_STATUS = b"\x27" # Status register +LPS_PRESS_OUT_XL = b"\x28" # Pressure output registers +LPS_PRESS_OUT_L = b"\x29" +LPS_PRESS_OUT_H = b"\x2A" +LPS_TEMP_OUT_L = b"\x2B" # Temperature output registers +LPS_TEMP_OUT_H = b"\x2C" +LPS_RES = b"\x33" # Filter reset register + + +class Lps22hb(I2CIOWrapper): + + def init(self): + chip_id = self.getChipId() + if chip_id != LPS22HB_CHIP_ID: + raise ValueError("{} got Wrong chip id: 0x{:02X}".format(type(self).__name__, chip_id)) + self.reset() # Wait for reset to complete + self.write(LPS_CTRL_REG1, b"\x02") # Low-pass filter disabled , output registers not updated until MSB and LSB have been read , Enable Block Data Update , Set Output Data Rate to 0 + + def getChipId(self): + return self.read(LPS_WHO_AM_I)[0] + + def reset(self): + data = self.read(LPS_CTRL_REG2)[0] + data |= 0x04 + self.write(LPS_CTRL_REG2, bytes([data])) # SWRESET Set 1 + while data: + data = self.read(LPS_CTRL_REG2)[0] + data &= 0x04 + + def __startOneshot(self): + data = self.read(LPS_CTRL_REG3)[0] + data = self.read(LPS_CTRL_REG2)[0] + data |= 0x01 # ONE_SHOT Set 1 + self.write(LPS_CTRL_REG2, bytes([data])) + + def getTempAndPressure(self): + self.__startOneshot() + for _ in range(10): + status = self.read(LPS_STATUS)[0] + if not (status & 0x01 and status & 0x02): + continue + press_out_xl = self.read(LPS_PRESS_OUT_XL)[0] + press_out_l = self.read(LPS_PRESS_OUT_L)[0] + press_out_h = self.read(LPS_PRESS_OUT_H)[0] + press_data = ((press_out_h << 16) + (press_out_l << 8) + press_out_xl) / 4096.0 + temp_out_l = self.read(LPS_TEMP_OUT_L)[0] + temp_out_h = self.read(LPS_TEMP_OUT_H)[0] + temp_data =((temp_out_h << 8) + temp_out_l) / 100.0 + return round(press_data, 2), round(temp_data, 2) + else: + return 0, 0 + + +if __name__ == '__main__': + print("\nPressure Sensor Test Program ...\n") + from machine import I2C + + lps22hb=Lps22hb(I2C(I2C.I2C1, I2C.STANDARD_MODE), LPS22HB_SLAVE_ADDRESS) + lps22hb.init() + + for i in range(100): + try: + press_data, temp_data = lps22hb.getTempAndPressure() + utime.sleep_ms(100) + print('Pressure: {:6.2f} hPa, Temperature: {:6.2f} °C'.format(press_data or 0, temp_data or 0)) + except Exception as e: + print(e) + break diff --git a/EG912U-GL-Tracker/code/drivers/shtc3.py b/EG912U-GL-Tracker/code/drivers/shtc3.py new file mode 100644 index 0000000..f44d749 --- /dev/null +++ b/EG912U-GL-Tracker/code/drivers/shtc3.py @@ -0,0 +1,103 @@ +""" +SHTC3 temperature and humidity sensor driver. +The SHTC3 is a digital humidity and temperature sensor designed especially for battery-driven high-volume consumer electronics applications. +""" + +import utime +from usr.libs.i2c import I2CIOWrapper + + +SHTC3_SLAVE_ADDR = 0x70 + +# Commands +SHTC3_WAKEUP = b"\x35\x17" +SHTC3_SLEEP = b"\xB0\x98" +SHTC3_NM_CE_READ_TH = b"\x7C\xA2" +SHTC3_NM_CE_READ_RH = b"\x5C\x24" +SHTC3_NM_CD_READ_TH = b"\x78\x66" +SHTC3_NM_CD_READ_RH = b"\x58\xE0" +SHTC3_LM_CE_READ_TH = b"\x64\x58" +SHTC3_LM_CE_READ_RH = b"\x44\xDE" +SHTC3_LM_CD_READ_TH = b"\x60\x9C" +SHTC3_LM_CD_READ_RH = b"\x40\x1A" +SHTC3_SOFTWARE_RESET = b"\x40\x1A" +SHTC3_ID = b"\xEF\xC8" + + +class Shtc3(I2CIOWrapper): + + def init(self): + chip_id = self.getChipId() + if chip_id != 0x0807: + raise ValueError("{} get wrong chip id: {}".format(type(self).__name__, chip_id)) + self.soft_reset() + + def getChipId(self): + id = int.from_bytes(self.read(SHTC3_ID, 2), "big") + return id & 0x0807 + + def wakeup(self): + self.write(SHTC3_WAKEUP, b'') + utime.sleep_ms(30) + + def sleep(self): + self.write(SHTC3_SLEEP, b'') + + def soft_reset(self): + self.write(SHTC3_SOFTWARE_RESET, b'') + utime.sleep_ms(30) + + @staticmethod + def checkCrc(data, checksum): + crc = 0xFF + for one in data: + crc ^= one + for _ in range(8): + if(crc & 0x80): + crc = (crc << 1) ^ 0x131 + else: + crc = crc << 1 + return crc == checksum + + def __getValue(self): + utime.sleep_ms(20) + data = self.read(b'', 3) + if self.checkCrc(data[:2], data[2]): + return data[0] << 8 | data[1] + + def getTempValue(self): + """Calculate the temperature value.""" + self.write(b'', SHTC3_NM_CD_READ_TH) + value = self.__getValue() + if value is not None: + value = 175 * value / 65536.0 - 45.0 + return round(value, 2) + return 0 + + def getHumiValue(self): + """Calculate the humidity value.""" + self.write(b'', SHTC3_NM_CD_READ_RH) + value = self.__getValue() + if value is not None: + value = 100 * value / 65536.0 + return round(value, 2) + return 0 + + def getTempAndHumi(self): + self.wakeup() + temp = self.getTempValue() + humi = self.getHumiValue() + self.sleep() + return temp, humi + + +if __name__ == "__main__": + from machine import I2C + shtc3_dev = Shtc3(I2C(I2C.I2C1, I2C.STANDARD_MODE), SHTC3_SLAVE_ADDR) + shtc3_dev.init() + for i in range(100): + shtc3_dev.wakeup() + temp = shtc3_dev.getTempValue() + humi = shtc3_dev.getHumiValue() + shtc3_dev.sleep() + print("Temperature: {:.2f}°C , Humidity: {:.2f} %\n".format(temp, humi)) diff --git a/EG912U-GL-Tracker/code/drivers/tcs34725.py b/EG912U-GL-Tracker/code/drivers/tcs34725.py new file mode 100644 index 0000000..e04c43f --- /dev/null +++ b/EG912U-GL-Tracker/code/drivers/tcs34725.py @@ -0,0 +1,323 @@ +import utime as time +from usr.libs.i2c import I2CIOWrapper +from machine import ExtInt + + +TCS34725_SLAVE_ADDR = 0x29 + +TCS34725_R_Coef = 0.136 +TCS34725_G_Coef = 1.000 +TCS34725_B_Coef = -0.444 +TCS34725_GA = 1.0 +TCS34725_DF = 310.0 +TCS34725_CT_Coef = 3810.0 +TCS34725_CT_Offset = 1391.0 + +class Tcs34725(I2CIOWrapper): + + Gain_t = 0 + IntegrationTime_t = 0 + + TCS34725_CMD_BIT = 0x80 + TCS34725_CMD_ReadByte = 0x00 + TCS34725_CMD_Read_Word = 0x20 + TCS34725_CMD_Clear_INT = 0x66 + + TCS34725_ENABLE = 0x00 + TCS34725_ENABLE_AIEN = 0x10 # RGBC Interrupt Enable + TCS34725_ENABLE_WEN = 0x08 # Wait enable - Writing 1 activates the wait timer + TCS34725_ENABLE_AEN = 0x02 # RGBC Enable - Writing 1 actives the ADC, 0 disables it + TCS34725_ENABLE_PON = 0x01 # Power on - Writing 1 activates the internal oscillator, 0 disables it + TCS34725_ATIME = 0x01 # Integration time + TCS34725_WTIME = 0x03 # Wait time (if TCS34725_ENABLE_WEN is asserted) + TCS34725_WTIME_2_4MS = 0xFF # WLONG0 = 2.4ms WLONG1 = 0.029s + TCS34725_WTIME_204MS = 0xAB # WLONG0 = 204ms WLONG1 = 2.45s + TCS34725_WTIME_614MS = 0x00 # WLONG0 = 614ms WLONG1 = 7.4s + TCS34725_AILTL = 0x04 # Clear channel lower interrupt threshold + TCS34725_AILTH = 0x05 + TCS34725_AIHTL = 0x06 # Clear channel upper interrupt threshold + TCS34725_AIHTH = 0x07 + TCS34725_PERS = 0x0C # Persistence register - basic SW filtering mechanism for interrupts + TCS34725_PERS_NONE = 0b0000 # Every RGBC cycle generates an interrupt + TCS34725_PERS_1_CYCLE = 0b0001 # 1 clean channel value outside threshold range generates an interrupt + TCS34725_PERS_2_CYCLE = 0b0010 # 2 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_3_CYCLE = 0b0011 # 3 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_5_CYCLE = 0b0100 # 5 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_10_CYCLE = 0b0101 # 10 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_15_CYCLE = 0b0110 # 15 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_20_CYCLE = 0b0111 # 20 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_25_CYCLE = 0b1000 # 25 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_30_CYCLE = 0b1001 # 30 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_35_CYCLE = 0b1010 # 35 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_40_CYCLE = 0b1011 # 40 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_45_CYCLE = 0b1100 # 45 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_50_CYCLE = 0b1101 # 50 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_55_CYCLE = 0b1110 # 55 clean channel values outside threshold range generates an interrupt + TCS34725_PERS_60_CYCLE = 0b1111 # 60 clean channel values outside threshold range generates an interrupt + TCS34725_CONFIG = 0x0D + TCS34725_CONFIG_WLONG = 0x02 # Choose between short and long (12x) wait times via TCS34725_WTIME + TCS34725_CONTROL = 0x0F # Set the gain level for the sensor + TCS34725_ID = 0x12 # 0x44 = TCS34721/TCS34725, 0x4D = TCS34723/TCS34727 + TCS34725_STATUS = 0x13 + TCS34725_STATUS_AINT = 0x10 # RGBC Clean channel interrupt + TCS34725_STATUS_AVALID = 0x01 # Indicates that the RGBC channels have completed an integration cycle + TCS34725_CDATAL = 0x14 # Clear channel data + TCS34725_CDATAH = 0x15 + TCS34725_RDATAL = 0x16 # Red channel data + TCS34725_RDATAH = 0x17 + TCS34725_GDATAL = 0x18 # Green channel data + TCS34725_GDATAH = 0x19 + TCS34725_BDATAL = 0x1A # Blue channel data + TCS34725_BDATAH = 0x1B + + #Integration Time + TCS34725_INTEGRATIONTIME_2_4MS = 0xFF #< 2.4ms - 1 cycle - Max Count: 1024 + TCS34725_INTEGRATIONTIME_24MS = 0xF6 #< 24ms - 10 cycles - Max Count: 10240 + TCS34725_INTEGRATIONTIME_50MS = 0xEB #< 50ms - 20 cycles - Max Count: 20480 + TCS34725_INTEGRATIONTIME_101MS = 0xD5 #< 101ms - 42 cycles - Max Count: 43008 + TCS34725_INTEGRATIONTIME_154MS = 0xC0 #< 154ms - 64 cycles - Max Count: 65535 + TCS34725_INTEGRATIONTIME_700MS = 0x00 #< 700ms - 256 cycles - Max Count: 65535 + + #Gain + TCS34725_GAIN_1X = 0x00 #< No gain */ + TCS34725_GAIN_4X = 0x01 #< 4x gain */ + TCS34725_GAIN_16X = 0x02 #< 16x gain */ + TCS34725_GAIN_60X = 0x03 #< 60x gain */ + + + def __init__(self, i2c, slaveaddr=0x29, debug=False): + super().__init__(i2c, slaveaddr) + self.debug = debug + #Set GPIO mode + self.INT = ExtInt(ExtInt.GPIO29, ExtInt.IRQ_FALLING, ExtInt.PULL_PU, lambda args: print(args)) + self.INT.enable() + if (self.debug): + print("Reseting TSL2581") + + def writeByte(self, reg, value): + # "Writes an 8-bit value to the specified register/address" + reg = reg | self.TCS34725_CMD_BIT # Register addressing highest bit is set to 1 + super().writeByte(reg, value) + if (self.debug): + print("I2C: Write 0x%02X to register 0x%02X" % (value, reg)) + + def readByte(self, reg): + # "Read an unsigned byte from the I2C device" + reg = reg | self.TCS34725_CMD_BIT + result = super().readByte(reg) + if (self.debug): + print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg)) + return result + + def readWord(self, reg): + # "Read an unsigned byte from the I2C device" + reg = reg | self.TCS34725_CMD_BIT + result = int.from_bytes(self.read(bytes([reg]), size=2), "big") + if (self.debug): + print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg)) + return result + + def setGain(self, gain): + self.writeByte(self.TCS34725_CONTROL, gain) + self.Gain_t = gain + + def setIntegrationTime(self, time): + # Update the timing register + self.writeByte(self.TCS34725_ATIME, time) + self.IntegrationTime_t = time + + def enable(self): + self.writeByte(self.TCS34725_ENABLE, self.TCS34725_ENABLE_PON) + time.sleep(0.01) + self.writeByte(self.TCS34725_ENABLE, self.TCS34725_ENABLE_PON | self.TCS34725_ENABLE_AEN) + time.sleep(0.01) + + def disable(self): + #Turn the device off to save power + reg = self.readByte(self.TCS34725_ENABLE) + self.writeByte(self.TCS34725_ENABLE, reg & ~(self.TCS34725_ENABLE_PON | self.TCS34725_ENABLE_AEN)) + + def interruptEnable(self): + reg = self.readByte(self.TCS34725_ENABLE) + self.writeByte(self.TCS34725_ENABLE, reg | self.TCS34725_ENABLE_AIEN) + + def interruptDisable(self): + reg = self.readByte(self.TCS34725_ENABLE) + self.writeByte(self.TCS34725_ENABLE, reg & (~self.TCS34725_ENABLE_AIEN)) + + def Set_Interrupt_Persistence_Reg(self, PER): + if(PER < 0x10): + self.writeByte(self.TCS34725_PERS, PER) + else : + self.writeByte(self.TCS34725_PERS, self.TCS34725_PERS_60_CYCLE) + + def setInterruptThreshold(self, Threshold_H, Threshold_L): + self.writeByte(self.TCS34725_AILTL, Threshold_L & 0xff) + self.writeByte(self.TCS34725_AILTH, Threshold_L >> 8) + self.writeByte(self.TCS34725_AIHTL, Threshold_H & 0xff) + self.writeByte(self.TCS34725_AIHTH, Threshold_H >> 8) + + def clearInterruptFlag(self): + self.writeByte(self.TCS34725_CMD_Clear_INT, 0x00) + + def init(self): + chip_id = self.readByte(self.TCS34725_ID) + if chip_id not in (0x44, 0x4D): + raise ValueError("Device ID is not correct") + self.setIntegrationTime(self.TCS34725_INTEGRATIONTIME_154MS) + self.setGain(self.TCS34725_GAIN_60X) + self.IntegrationTime_t = self.TCS34725_INTEGRATIONTIME_154MS + self.Gain_t = self.TCS34725_GAIN_60X + self.enable() + self.interruptEnable() + + + def getLuxInterrupt(self, Threshold_H, Threshold_L): + self.setInterruptThreshold(Threshold_H, Threshold_L) + if(self.INT.read_level() == 0): + self.clearInterruptFlag() + self.Set_Interrupt_Persistence_Reg(self.TCS34725_PERS_2_CYCLE) + return 1 + + return 0 + + def getChipId(self): + return self.readByte(self.TCS34725_ID) + + def getRGBData(self): + self.C = self.readWord(self.TCS34725_CDATAL | self.TCS34725_CMD_Read_Word) + self.R = self.readWord(self.TCS34725_RDATAL | self.TCS34725_CMD_Read_Word) + self.G = self.readWord(self.TCS34725_GDATAL | self.TCS34725_CMD_Read_Word) + self.B = self.readWord(self.TCS34725_BDATAL | self.TCS34725_CMD_Read_Word) + if(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_2_4MS): + time.sleep(0.01) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_24MS): + time.sleep(0.04) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_50MS): + time.sleep(0.05) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_101MS): + time.sleep(0.1) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_154MS): + time.sleep(0.2) + elif(self.IntegrationTime_t == self.TCS34725_INTEGRATIONTIME_700MS): + time.sleep(0.7) + + #Convert read data to RGB888 format + def getRGB888(self): + i = 1 + if(self.R >= self.G and self.R >= self.B): + i = self.R // 255 + 1 + + elif( self.G >= self.R and self.G >= self.B): + i = self.G // 255 + 1 + + elif( self.B >= self.G and self.B >= self.R): + i = self.B // 255 + 1 + + if(i!=0): + self.RGB888_R = self.R // i + self.RGB888_G = self.G // i + self.RGB888_B = self.B // i + + if(self.RGB888_R > 30): + self.RGB888_R = self.RGB888_R - 30 + if(self.RGB888_G > 30): + self.RGB888_G = self.RGB888_G - 30 + if(self.RGB888_B > 30): + self.RGB888_B = self.RGB888_B - 30 + + self.RGB888_R = self.RGB888_R * 255 // 225 + self.RGB888_G = self.RGB888_G * 255 // 225 + self.RGB888_B = self.RGB888_B * 255 // 225 + self.RGB888 = (self.RGB888_R<<16) | (self.RGB888_G<<8) | (self.RGB888_B) + + def getRGB565(self): + i = 1 + RGB565_R = 0 + RGB565_G = 0 + RGB565_B = 0 + + if(self.R >= self.G and self.R >= self.B): + i = self.R // 255 + 1 + elif( self.G >= self.R and self.G >= self.B): + i = self.G // 255 + 1 + elif( self.B >= self.G and self.B >= self.R): + i = self.B // 255 + 1 + + if(i!=0): + RGB565_R = self.R // i + RGB565_G = self.G // i + RGB565_B = self.B // i + + if(RGB565_R > 30): + RGB565_R = RGB565_R - 30 + if(RGB565_G > 30): + RGB565_G = RGB565_G - 30 + if(RGB565_B > 30): + RGB565_B = RGB565_B - 30 + + RGB565_R = RGB565_R * 255 // 225 + RGB565_G = RGB565_G * 255 // 225 + RGB565_B = RGB565_B * 255 // 225 + self.RG565 = (((RGB565_R>>3) << 11) | ((RGB565_G>>2) << 5) | (RGB565_B>>3 ))&0xffff + + def getLux(self): + atime_ms = ((256 - self.IntegrationTime_t) * 2.4) + if(self.R + self.G + self.B > self.C): + ir = (self.R + self.G + self.B - self.C) / 2 + else: + ir = 0 + r_comp = self.R - ir + g_comp = self.G - ir + b_comp = self.B - ir + Gain_temp = 1 + if(self.Gain_t == self.TCS34725_GAIN_1X): + Gain_temp = 1 + elif(self.Gain_t == self.TCS34725_GAIN_4X): + Gain_temp = 4 + elif(self.Gain_t == self.TCS34725_GAIN_16X): + Gain_temp = 16 + elif(self.Gain_t == self.TCS34725_GAIN_60X): + Gain_temp = 60 + + cpl = (atime_ms * Gain_temp) / (TCS34725_GA * TCS34725_DF) + lux = (TCS34725_R_Coef * (float)(r_comp) + TCS34725_G_Coef * \ + (float)(g_comp) + TCS34725_B_Coef * (float)(b_comp)) / cpl + return lux + + def getColorTemp(self): + ir=1.0 + if(self.R + self.G + self.B > self.C): + ir = (self.R + self.G + self.B - self.C - 1) / 2 + else: + ir = 0 + r_comp = self.R - ir + b_comp = self.B - ir + cct=TCS34725_CT_Coef * (float)(b_comp) / (float)(r_comp) + TCS34725_CT_Offset + return cct + + def getRGBValue(self): + self.getRGBData() + self.getRGB888() + return self.RGB888 + + +if __name__ == "__main__": + from machine import I2C + tcs34725 = Tcs34725(I2C(I2C.I2C1, I2C.STANDARD_MODE), TCS34725_SLAVE_ADDR) + tcs34725.init() + + time.sleep(2) + for _ in range(20): + tcs34725.getRGBData() + tcs34725.getRGB888() + tcs34725.getRGB565() + print("R: %d " % tcs34725.RGB888_R, end="") + print("G: %d " % tcs34725.RGB888_G, end="") + print("B: %d " % tcs34725.RGB888_B, end="") + print("C: %#x " % tcs34725.C, end="") + print("RGB565: %#x " % tcs34725.RG565, end="") + print("RGB888: %#x " % tcs34725.RGB888, end="") + print("LUX: %d " % tcs34725.getLux(), end="") + print("CT: %dK " % tcs34725.getColorTemp(), end="") + print("INT: %d " % tcs34725.getLuxInterrupt(0xff00, 0x00ff)) diff --git a/EG912U-GL-Tracker/code/extensions/__init__.py b/EG912U-GL-Tracker/code/extensions/__init__.py new file mode 100644 index 0000000..8a3dda3 --- /dev/null +++ b/EG912U-GL-Tracker/code/extensions/__init__.py @@ -0,0 +1,10 @@ +from .qth_client import QthClient +from .gnss_service import GnssService +from .lbs_service import LbsService +from .sensor_service import SensorService + + +qth_client = QthClient() +gnss_service = GnssService() +lbs_service = LbsService() +sensor_service = SensorService() diff --git a/EG912U-GL-Tracker/code/extensions/gnss_service.py b/EG912U-GL-Tracker/code/extensions/gnss_service.py new file mode 100644 index 0000000..c82c470 --- /dev/null +++ b/EG912U-GL-Tracker/code/extensions/gnss_service.py @@ -0,0 +1,226 @@ +import utime +import quecgnss +from usr.libs import CurrentApp +from usr.libs.threading import Thread +from usr.libs.logging import getLogger +import _thread +from .import qth_client +try: + from math import sin, asin, cos, radians, fabs, sqrt +except: + from cmath import sin as csin, cos as ccos, pi + + def radians(x): + return x * pi / 180.0 + + def fabs(x): + return x if x > 0 else -x + + def sin(x): + return csin(x).real + + def cos(x): + return ccos(x).real + + def asin(x): + low, high = -1, 1 + while abs(high - low) > 1e-10: # 精度控制 + mid = (low + high) / 2.0 + if sin(mid) < x: + low = mid + else: + high = mid + return (low + high) / 2.0 + + +logger = getLogger(__name__) + + +EARTH_RADIUS = 6371 # 地球平均半径大约6371km +GLOBAL_DISTANCE = 0 # 里程km + + +def hav(theta): + s = sin(theta / 2) + return s * s + + +def gps_distance(lat0, lng0, lat1, lng1): + # 用haversine公式计算球面两点间的距离 + # 经纬度转换成弧度 + lat0 = radians(lat0) + lat1 = radians(lat1) + lng0 = radians(lng0) + lng1 = radians(lng1) + dlng = fabs(lng0 - lng1) + dlat = fabs(lat0 - lat1) + h = hav(dlat) + cos(lat0) * cos(lat1) * hav(dlng) + distance = 2 * EARTH_RADIUS * asin(pow(h, 0.5)) # km + # distance = int(distance * 1000) # m + return distance + + +class NmeaDict(dict): + + @classmethod + def load(cls, raw): + items = {} + for line in raw.split('\r\n'): + try: + tail_index = line.rfind('*') + if tail_index == -1: + continue + head_index = line.rfind('$', 0, tail_index) + if head_index == -1: + continue + crc = int(line[tail_index + 1:tail_index + 3], 16) + if cls.checksum(line[head_index + 1:tail_index]) != crc: + raise ValueError('CRC check failed') + cmdlist = line[head_index:tail_index].split(',') + # print(line[head_index:]) + if cmdlist[0] not in items: + items[cmdlist[0]] = [] + items[cmdlist[0]].append(line) + except Exception as e: + # logger.debug('parse nmea line error: {}; pass it: {}'.format(e, line)) + continue + return cls(items) + + @staticmethod + def checksum(data): + crc = ord(data[0]) + for one in (ord(_) for _ in data[1:]): + crc ^= one + return crc + + +class GnssService(object): + + def __init__(self, app=None): + self.__gnss = quecgnss + if app is not None: + self.init_app(app) + + def __str__(self): + return '{}'.format(type(self).__name__) + + def init_app(self, app): + app.register('gnss_service', self) + + def load(self): + logger.info('loading {} extension, init quecgnss will take some seconds'.format(self)) + result = self.init() + logger.info('{} init gnss res: {}'.format(self, result)) + if result: + Thread(target=self.start_update).start() + + def init(self): + if self.__gnss.init() != 0: + logger.warn('{} gnss init FAILED'.format(self)) + return False + return True + + def status(self): + # 0 int GNSS模块处于关闭状态 + # 1 int GNSS模块固件升级中 + # 2 int GNSS模块定位中,这种模式下即可开始读取GNSS定位数据,定位数据是否有效需要用户获取到定位数据后,解析对应语句来判断,比如判断GNRMC语句的status是 A 还是 V,A 表示定位有效,V表示定位无效。 + return self.__gnss.get_state() + + def enable(self, flag=True): + return self.__gnss.gnssEnable(bool(flag)) == 0 + + def read(self, size=4096): + raw = self.__gnss.read(size) + if raw != -1: + size, data = raw + # logger.debug('gnss read raw {} bytes data:\n{}'.format(size, data)) + return NmeaDict.load(data) + + def start_update(self): + prev_lat_and_lng = None + + while True: + nmea_dict = self.read() + if nmea_dict is None: + utime.sleep(3) + continue + + nmea_data = None + + if nmea_data is None: + if "$GNRMC" in nmea_dict: + for temp in nmea_dict["$GNRMC"]: + nmea_tuple = temp.split(",") + if nmea_tuple[2] == "A": + nmea_data = temp + + lat_string = nmea_tuple[3] + lat_high = float(lat_string[:2]) + lat_low = float(lat_string[2:]) / 60 + lat = lat_high + lat_low + if nmea_tuple[4] == "S": + lat = -lat + + lng_string = nmea_tuple[5] # 11755.787896484374(单位:分) + lng_high = float(lng_string[:3]) + lng_low = float(lng_string[3:]) / 60 + lng = lng_high + lng_low + if nmea_tuple[6] == "W": + lng = -lng + + break + + if nmea_data is None: + if "$GNGGA" in nmea_dict: + for temp in nmea_dict["$GNGGA"]: + nmea_tuple = temp.split(",") + if nmea_tuple[6] != "0": + nmea_data = temp + + lat_string = nmea_tuple[2] + lat_high = float(lat_string[:2]) + lat_low = float(lat_string[2:]) / 60 + lat = lat_high + lat_low + if nmea_tuple[3] == "S": + lat = -lat + + lng_string = nmea_tuple[4] # 11755.787896484374(单位:分) + lng_high = float(lng_string[:3]) + lng_low = float(lng_string[3:]) / 60 + lng = lng_high + lng_low + if nmea_tuple[5] == "W": + lng = -lng + + break + logger.debug("data: {}".format(nmea_data)) + logger.debug("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + if nmea_data is not None: + # logger.debug("GPS data: {}".format(nmea_data)) + # logger.debug("prev_lat_and_lng: {}".format(prev_lat_and_lng)) + logger.debug("lat_and_lng: {}".format((lat, lng))) + if prev_lat_and_lng is None: + # 首次定位 + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendGnss(nmea_data): + prev_lat_and_lng = (lat, lng) + logger.error("send gnss to qth server success") + break + else: + logger.error("send gnss to qth server fail") + else: + # 或者位移超过 50m,则上报 + distance = gps_distance(prev_lat_and_lng[0], prev_lat_and_lng[1], lat, lng) + logger.debug('distance delta: {:f}'.format(distance)) + if distance >= 0.05: + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendGnss(nmea_data): + prev_lat_and_lng = (lat, lng) + logger.error("send gnss to qth server success") + break + else: + logger.error("send gnss to qth server fail") + utime.sleep(3) + + diff --git a/EG912U-GL-Tracker/code/extensions/lbs_service.py b/EG912U-GL-Tracker/code/extensions/lbs_service.py new file mode 100644 index 0000000..45ef57f --- /dev/null +++ b/EG912U-GL-Tracker/code/extensions/lbs_service.py @@ -0,0 +1,84 @@ +import net +import utime +from usr.libs import CurrentApp +from usr.libs.threading import Thread +from usr.libs.logging import getLogger +import _thread + +logger = getLogger(__name__) + + +class LbsService(object): + + def __init__(self, app=None): + self.__net = net + if app is not None: + self.init_app(app) + + def __str__(self): + return '{}'.format(type(self).__name__) + + + def init_app(self, app): + app.register('lbs_service', self) + + def load(self): + logger.info('loading {} extension, init lbs will take some seconds'.format(self)) + Thread(target=self.start_update).start() + + def read(self): + cell_info = net.getCellInfo() + if cell_info != -1 and cell_info[2]: + first_tuple = cell_info[2] + mcc_decimal = first_tuple[0][2] # 获取十进制MCC (如1120) + mcc_hex = "{:x}".format(mcc_decimal).upper() # 转换为十六进制 (如'460') + + lbs_data = "$LBS,{},{},{},{},{},0*69;".format( + mcc_hex, + first_tuple[0][3], + first_tuple[0][5], + first_tuple[0][1], + first_tuple[0][7] + ) + return lbs_data + + def start_update(self): + while True: + lbs_data = self.read() + if lbs_data is None: + utime.sleep(2) + continue + + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendLbs(lbs_data): + break + else: + logger.debug("send lbs data to qth server fail, next report will be after 2 seconds") + utime.sleep(2) + continue + + logger.debug("send lbs data to qth server success, next report will be after 1800 seconds") + utime.sleep(1800) + + def put_lbs(self): + while True: + lbs_data = self.read() + if lbs_data is None: + utime.sleep(2) + continue + + for _ in range(3): + with CurrentApp().qth_client: + if CurrentApp().qth_client.sendLbs(lbs_data): + break + else: + logger.debug("send lbs data to qth server fail, next report will be after 2 seconds") + utime.sleep(2) + continue + + logger.debug("send LBS data to qth server success") + break + + + diff --git a/EG912U-GL-Tracker/code/extensions/qth_client.py b/EG912U-GL-Tracker/code/extensions/qth_client.py new file mode 100644 index 0000000..8f173b3 --- /dev/null +++ b/EG912U-GL-Tracker/code/extensions/qth_client.py @@ -0,0 +1,115 @@ +from usr.libs.threading import Lock +from usr.libs.logging import getLogger +from usr import Qth +from usr.libs import CurrentApp +from . import lbs_service +logger = getLogger(__name__) + + +class QthClient(object): + + def __init__(self, app=None): + self.opt_lock = Lock() + if app: + self.init_app(app) + + def __enter__(self): + self.opt_lock.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.opt_lock.release() + + def init_app(self, app): + app.register("qth_client", self) + Qth.init() + Qth.setProductInfo(app.config["QTH_PRODUCT_KEY"], app.config["QTH_PRODUCT_SECRET"]) + Qth.setServer(app.config["QTH_SERVER"]) + Qth.setEventCb( + { + "devEvent": self.eventCallback, + "recvTrans": self.recvTransCallback, + "recvTsl": self.recvTslCallback, + "readTsl": self.readTslCallback, + "readTslServer": self.recvTslServerCallback, + "ota": { + "otaPlan":self.otaPlanCallback, + "fotaResult":self.fotaResultCallback + } + } + ) + + def load(self): + self.start() + + def start(self): + Qth.start() + + def stop(self): + Qth.stop() + def sendTsl(self, mode, value): + return Qth.sendTsl(mode, value) + + def isStatusOk(self): + return Qth.state() + + def sendLbs(self, lbs_data): + return Qth.sendOutsideLocation(lbs_data) + + def sendGnss(self, nmea_data): + return Qth.sendOutsideLocation(nmea_data) + + def eventCallback(self, event, result): + logger.info("dev event:{} result:{}".format(event, result)) + if(2== event and 0 == result): + Qth.otaRequest() + + def recvTransCallback(self, value): + ret = Qth.sendTrans(1, value) + logger.info("recvTrans value:{} ret:{}".format(value, ret)) + + def recvTslCallback(self, value): + logger.info("recvTsl:{}".format(value)) + for cmdId, val in value.items(): + logger.info("recvTsl {}:{}".format(cmdId, val)) + def readTslCallback(self, ids, pkgId): + logger.info("readTsl ids:{} pkgId:{}".format(ids, pkgId)) + value=dict() + + temp1, humi =CurrentApp().sensor_service.get_temp1_and_humi() + press, temp2 = CurrentApp().sensor_service.get_press_and_temp2() + r,g,b = CurrentApp().sensor_service.get_rgb888() + + + for id in ids: + if 3 == id: + value[3]=temp1 + elif 4 == id: + value[4]=humi + elif 5 == id: + value[5]=temp2 + elif 6 == id: + value[6]=press + elif 7 == id: + value[7]={1:r, 2:g, 3:b} + Qth.ackTsl(1, value, pkgId) + + + def recvTslServerCallback(self, serverId, value, pkgId): + logger.info("recvTslServer serverId:{} value:{} pkgId:{}".format(serverId, value, pkgId)) + Qth.ackTslServer(1, serverId, value, pkgId) + + def otaPlanCallback(self, plans): + logger.info("otaPlan:{}".format(plans)) + Qth.otaAction(1) + + def fotaResultCallback(self, comp_no, result): + logger.info("fotaResult comp_no:{} result:{}".format(comp_no, result)) + + def sotaInfoCallback(self, comp_no, version, url, md5, crc): + logger.info("sotaInfo comp_no:{} version:{} url:{} md5:{} crc:{}".format(comp_no, version, url, md5, crc)) + # 当使用url下载固件完成,且MCU更新完毕后,需要获取MCU最新的版本信息,并通过setMcuVer进行更新 + Qth.setMcuVer("MCU1", "V1.0.0", self.sotaInfoCallback, self.sotaResultCallback) + + def sotaResultCallback(comp_no, result): + logger.info("sotaResult comp_no:{} result:{}".format(comp_no, result)) diff --git a/EG912U-GL-Tracker/code/extensions/sensor_service.py b/EG912U-GL-Tracker/code/extensions/sensor_service.py new file mode 100644 index 0000000..c827941 --- /dev/null +++ b/EG912U-GL-Tracker/code/extensions/sensor_service.py @@ -0,0 +1,143 @@ +import utime +from machine import I2C +from usr.libs import CurrentApp +from usr.libs.threading import Thread +from usr.libs.logging import getLogger +from usr.drivers.shtc3 import Shtc3, SHTC3_SLAVE_ADDR +from usr.drivers.lps22hb import Lps22hb, LPS22HB_SLAVE_ADDRESS +from usr.drivers.tcs34725 import Tcs34725, TCS34725_SLAVE_ADDR + + +logger = getLogger(__name__) + + +class SensorService(object): + + def __init__(self, app=None): + # i2c channel 0 + self.i2c_channel0 = I2C(I2C.I2C1, I2C.STANDARD_MODE) + # SHTC3 + self.shtc3 = Shtc3(self.i2c_channel0, SHTC3_SLAVE_ADDR) + self.shtc3.init() + # LPS22HB + self.lps22hb = Lps22hb(self.i2c_channel0, LPS22HB_SLAVE_ADDRESS) + self.lps22hb.init() + # TCS34725 + self.tcs34725 = Tcs34725(self.i2c_channel0, TCS34725_SLAVE_ADDR) + self.tcs34725.init() + + if app is not None: + self.init_app(app) + + def __str__(self): + return '{}'.format(type(self).__name__) + + def init_app(self, app): + app.register('sensor_service', self) + + def load(self): + logger.info('loading {} extension, init sensors will take some seconds'.format(self)) + Thread(target=self.start_update).start() + + + def get_temp1_and_humi(self): + return self.shtc3.getTempAndHumi() + + def get_press_and_temp2(self): + return self.lps22hb.getTempAndPressure() + def get_rgb888(self): + rgb888 = self.tcs34725.getRGBValue() + logger.debug("R: {}, G: {}, B: {}".format((rgb888 >> 16) & 0xFF, (rgb888 >> 8) & 0xFF, rgb888 & 0xFF)) + + r = (rgb888 >> 16) & 0xFF + g = (rgb888 >> 8) & 0xFF + b = rgb888 & 0xFF + return r, g, b + + def start_update(self): + prev_temp1 = None + prev_humi = None + prev_press = None + prev_temp2 = None + prev_rgb888 = None + + + while True: + data = {} + + try: + temp1, humi = self.shtc3.getTempAndHumi() + logger.debug("temp1: {:0.2f}, humi: {:0.2f}".format(temp1, humi)) + + if prev_temp1 is None or abs(prev_temp1 - temp1) > 1: + data.update({3: round(temp1, 2)}) + prev_temp1 = temp1 + + if prev_humi is None or abs(prev_humi - humi) > 1: + data.update({4: round(humi, 2)}) + prev_humi = humi + + except Exception as e: + logger.error("getTempAndHumi error:{}".format(e)) + + utime.sleep_ms(100) + + try: + press, temp2 = self.lps22hb.getTempAndPressure() + logger.debug("press: {:0.2f}, temp2: {:0.2f}".format(press, temp2)) + + if prev_temp2 is None or abs(prev_temp2 - temp2) > 1: + data.update({5: round(temp2, 2)}) + prev_temp2 = temp2 + + if prev_press is None or abs(prev_press - press) > 1: + data.update({6: round(press, 2)}) + prev_press = press + + except Exception as e: + logger.error("getTempAndPressure error:{}".format(e)) + + utime.sleep_ms(100) + + try: + rgb888 = self.tcs34725.getRGBValue() + logger.debug("R: {}, G: {}, B: {}".format((rgb888 >> 16) & 0xFF, (rgb888 >> 8) & 0xFF, rgb888 & 0xFF)) + + r = (rgb888 >> 16) & 0xFF + g = (rgb888 >> 8) & 0xFF + b = rgb888 & 0xFF + + if prev_rgb888 is None: + data.update({7: {1: r, 2: g, 3: b}}) + prev_rgb888 = rgb888 + else: + prev_r = (prev_rgb888 >> 16) & 0xFF + dr = abs(r - prev_r) + + prev_g = (prev_rgb888 >> 8) & 0xFF + dg = abs(g - prev_g) + + prev_b = prev_rgb888 & 0xFF + db = abs(b - prev_b) + + # 色差超过 150 即认为颜色有变化 + if pow(sum((dr*dr, dg*dg, db*db)), 0.5) >= 150: + data.update({7: {1: r, 2: g, 3: b}}) + prev_rgb888 = rgb888 + + except Exception as e: + logger.error("getRGBValue error:{}".format(e)) + + if data: + with CurrentApp().qth_client: + for _ in range(3): + if CurrentApp().qth_client.sendTsl(1, data): + break + else: + prev_temp1 = None + prev_humi = None + prev_press = None + prev_temp2 = None + prev_rgb888 = None + + utime.sleep(1) diff --git a/EG912U-GL-Tracker/code/libs/__init__.py b/EG912U-GL-Tracker/code/libs/__init__.py new file mode 100644 index 0000000..b4838d8 --- /dev/null +++ b/EG912U-GL-Tracker/code/libs/__init__.py @@ -0,0 +1,96 @@ +import sys +import net +import sim +import modem +from misc import Power +from .common import Storage +from .collections import OrderedDict, Singleton + + +@Singleton +class _AppCtxGlobals(object): + + def setDefault(self, name, value): + if hasattr(self, name): + return getattr(self, name) + else: + setattr(self, name, value) + return value + + def get(self, name, default=None): + if hasattr(self, name): + return getattr(self, name) + else: + return default + + def set(self, name, value): + setattr(self, name, value) + + +G = _AppCtxGlobals + + +@Singleton +class Application(object): + """Application Class""" + + def __init__(self, name, version='1.0.0'): + self.__name = name + self.config = Storage() + self.__version = version + self.__extensions = OrderedDict() + + def __repr__(self): + return '{}(name=\"{}\", version=\"{}\")'.format(type(self).__name__, self.name, self.version) + + def __getattr__(self, name): + return self.__extensions[name] + + def register(self, name, ext): + if name in self.__extensions: + raise ValueError('extension name \"{}\" already in use'.format(name)) + self.__extensions[name] = ext + + def __powerOnPrintOnce(self): + output = '==================================================\r\n' + output += 'APP_NAME : {}\r\n' + output += 'APP_VERSION : {}\r\n' + output += 'FIRMWARE_VERSION : {}\r\n' + output += 'POWERON_REASON : {}\r\n' + output += 'DEVICE_IMEI : {}\r\n' + output += 'SIM_STATUS : {}\r\n' + output += 'NET_STATUS : {}\r\n' + output += '==================================================' + print(output.format( + self.name, + self.version, + modem.getDevFwVersion(), + Power.powerOnReason(), + modem.getDevImei(), + sim.getStatus(), + net.getState()[1][0] + )) + + def __loadExtensions(self): + for ext in self.__extensions.values(): + if not hasattr(ext, 'load'): + continue + try: + ext.load() + except Exception as e: + sys.print_exception(e) + + def run(self): + self.__powerOnPrintOnce() + self.__loadExtensions() + + @property + def version(self): + return self.__version + + @property + def name(self): + return self.__name + + +CurrentApp = Application diff --git a/EG912U-GL-Tracker/code/libs/collections.py b/EG912U-GL-Tracker/code/libs/collections.py new file mode 100644 index 0000000..31b619b --- /dev/null +++ b/EG912U-GL-Tracker/code/libs/collections.py @@ -0,0 +1,199 @@ + +class Singleton(object): + + def __init__(self, cls): + self.cls = cls + self.instance = None + + def __call__(self, *args, **kwargs): + if self.instance is None: + self.instance = self.cls(*args, **kwargs) + return self.instance + + def __repr__(self): + return repr(self.cls) + + +class _Node(object): + + def __init__(self, obj, next_=None, prev=None): + self.obj = obj + self.next = next_ + self.prev = prev + + def __repr__(self): + return '{}(obj={})'.format(type(self).__name__, repr(self.obj)) + + +class DoubleLinkList(object): + + def __init__(self): + self.__root = _Node(None) + self.__root.next = self.__root + self.__root.prev = self.__root + + def __iter__(self): + curr = self.__root.next + while curr != self.__root: + yield curr + curr = curr.next + + def __len__(self): + result = 0 + for _ in self: + result += 1 + return result + + def is_empty(self): + return self.__root.next is None + + def add(self, obj): + """头插""" + node = _Node(obj, next_=self.__root.next, prev=self.__root) + self.__root.next.prev = node + self.__root.next = node + return node + + def append(self, obj): + """尾插""" + node = _Node(obj, next_=self.__root, prev=self.__root.prev) + self.__root.prev.next = node + self.__root.prev = node + return node + + def insert(self, obj1, obj2): + """指定位置插入(将obj2插入obj1前) + + :param obj1: 节点数据域元素 + :param obj2: 节点数据域元素 + :return: obj2所属节点_Node + """ + pos = self.search(obj2) + if pos is None: + raise ValueError('{} not in list'.format(obj1)) + node = _Node(obj2, next_=pos, prev=pos.prev) + pos.prev.next = node + pos.prev = node + return node + + def search(self, obj): + """查找data所属节点,obj的类型必须实现__eq__ + + :param obj: 链表节点数据域元素 + :return: _Node节点 或 None + """ + for node in self: + if node.obj == obj: + return node + + def remove(self, obj): + """删除obj所属节点 + + :param obj: 链表节点数据域元素 + :return: None + :raise: 元素节点不存在抛ValueError + """ + node = self.search(obj) + if node is None: + raise ValueError('{} not in link'.format(obj)) + node.prev.next = node.next + node.next.prev = node.prev + + +class OrderedDict(object): + + def __init__(self, iterable=None): + self.__keys_link = DoubleLinkList() + self.__key_node_map = {} + self.__storage = {} + if isinstance(iterable, (tuple, list)): + self.__load(iterable) + + def __load(self, sequence): + for k, v in sequence: + self[k] = v + + def __repr__(self): + return '{}({})'.format(type(self).__name__, [(k, v) for k, v in self.items()]) + + def __iter__(self): + return (node.obj for node in self.__keys_link) + + def __setitem__(self, key, value): + if key not in self.__storage: + self.__key_node_map[key] = self.__keys_link.append(key) + self.__storage[key] = value + + def __getitem__(self, item): + return self.__storage[item] + + def __delitem__(self, key): + del self.__storage[key] + node = self.__key_node_map.pop(key) + node.prev.next = node.next + node.next.prev = node.prev + + def keys(self): + return iter(self) + + def values(self): + return (self.__storage[key] for key in self) + + def items(self): + return ((k, self.__storage[k]) for k in self) + + def get(self, key, default=None): + if key not in self.__storage: + return default + return self.__storage[key] + + def pop(self, key, default=None): + if key not in self.__storage: + return default + temp = self[key] + del self[key] + return temp + + def update(self, obj): + for k, v in obj.items(): + self[k] = v + + def setdefault(self, key, value): + if key in self.__storage: + return self[key] + else: + self[key] = value + return value + + +class Integer(object): + """serialize signed/unsigned Integer to/from bytes""" + + def __init__(self, value): + self.__value = value + + @property + def value(self): + return self.__value + + def toBytes(self, length=1, byteorder='big', signed=False): + if byteorder == 'little': + order = range(length) + elif byteorder == 'big': + order = reversed(range(length)) + else: + raise ValueError("byteorder must be either 'little' or 'big'") + return bytes((self.value >> i * 8) & 0xff for i in order) + + @classmethod + def fromBytes(cls, raw, byteorder='big', signed=False): + if byteorder == 'little': + little_ordered = list(raw) + elif byteorder == 'big': + little_ordered = list(reversed(raw)) + else: + raise ValueError("byteorder must be either 'little' or 'big'") + n = sum(b << i * 8 for i, b in enumerate(little_ordered)) + if signed and little_ordered and (little_ordered[-1] & 0x80): + n -= 1 << 8 * len(little_ordered) + return n diff --git a/EG912U-GL-Tracker/code/libs/common.py b/EG912U-GL-Tracker/code/libs/common.py new file mode 100644 index 0000000..bf9671e --- /dev/null +++ b/EG912U-GL-Tracker/code/libs/common.py @@ -0,0 +1,48 @@ +import ql_fs +from .threading import Lock + + +def deepcopy(obj): + if isinstance(obj, (int, float, str, bool, type(None))): + return obj + if isinstance(obj, (list, tuple, set)): + return type(obj)((deepcopy(item) for item in obj)) + elif isinstance(obj, dict): + return {k: deepcopy(v) for k, v in obj.items()} + else: + raise TypeError('unsupported for \"{}\" type'.format(type(obj))) + + +class Storage(dict): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__lock__ = Lock() + self.__storage_path__ = None + + def __enter__(self): + self.__lock__.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.__lock__.release() + + def __from_json(self, path): + if self.__storage_path__ is not None: + raise ValueError('storage already init from \"{}\"'.format(self.__storage_path__)) + if not ql_fs.path_exists(path): + ql_fs.touch(path, {}) + else: + self.update(ql_fs.read_json(path)) + + def init(self, path): + if path.endswith('.json'): + self.__from_json(path) + else: + raise ValueError('\"{}\" file type not supported'.format(path)) + self.__storage_path__ = path + + def save(self): + if self.__storage_path__ is None: + raise ValueError('storage path not existed, did you init?') + ql_fs.touch(self.__storage_path__, self) diff --git a/EG912U-GL-Tracker/code/libs/i2c.py b/EG912U-GL-Tracker/code/libs/i2c.py new file mode 100644 index 0000000..db59174 --- /dev/null +++ b/EG912U-GL-Tracker/code/libs/i2c.py @@ -0,0 +1,44 @@ +from machine import I2C +from usr.libs.collections import Integer + + +class I2CIOWrapper(object): + + class I2CReadError(Exception): + pass + + class I2CWriteError(Exception): + pass + + def __init__(self, i2c, slaveaddr): + if not isinstance(i2c, I2C): + raise TypeError('`i2c` should be machine.I2C type') + self.__i2c = i2c + self.__slaveaddr = slaveaddr + + + def read(self, addr, size=1, delay=0): + if size <= 0: + raise ValueError('`size` should be greater than 0') + data = bytearray(size) + if self.__i2c.read(self.__slaveaddr, addr, len(addr), data, size, delay) != 0: + raise self.I2CReadError("slave 0x{:X} read failed".format(self.__slaveaddr)) + return data + + def write(self, addr, data): + if not isinstance(data, (bytearray, bytes)): + raise TypeError('`data` should be bytearray or bytes') + if self.__i2c.write(self.__slaveaddr, addr, len(addr), data, len(data)) != 0: + raise self.I2CWriteError("slave 0x{:X} write failed".format(self.__slaveaddr)) + + def readByte(self, addr, byteorder="big", signed=False): + return Integer.fromBytes(self.read(b'' if addr is None else bytes([addr]), 1), byteorder=byteorder, signed=signed) + + def writeByte(self, addr, value): + return self.write(b'' if addr is None else bytes([addr]), bytes([value])) + + def readWord(self, addr, byteorder="big", signed=False): + return Integer.fromBytes(self.read(b'' if addr is None else bytes([addr]), 2), byteorder=byteorder, signed=signed) + + def writeWord(self, addr, value, byteorder="big"): + return self.write(b'' if addr is None else bytes([addr]), Integer(value).toBytes(2, byteorder=byteorder)) diff --git a/EG912U-GL-Tracker/code/libs/logging.py b/EG912U-GL-Tracker/code/libs/logging.py new file mode 100644 index 0000000..d43806d --- /dev/null +++ b/EG912U-GL-Tracker/code/libs/logging.py @@ -0,0 +1,131 @@ +import utime +import sys +import uio as io +import _thread + + +class Level(object): + DEBUG = 0 + INFO = 1 + WARN = 2 + ERROR = 3 + CRITICAL = 4 + + +_levelToName = { + Level.CRITICAL: 'CRITICAL', + Level.ERROR: 'ERROR', + Level.WARN: 'WARN', + Level.INFO: 'INFO', + Level.DEBUG: 'DEBUG' +} + +_nameToLevel = { + 'CRITICAL': Level.CRITICAL, + 'ERROR': Level.ERROR, + 'WARN': Level.WARN, + 'INFO': Level.INFO, + 'DEBUG': Level.DEBUG, +} + + +def getLevelName(level): + if level not in _levelToName: + raise ValueError('unknown level \"{}\", choose from .'.format(level)) + return _levelToName[level] + + +def getNameLevel(name): + temp = name.upper() + if temp not in _nameToLevel: + raise ValueError('\"{}\" is not valid. choose from [{}]'.format(name, list(_nameToLevel.keys()))) + return _nameToLevel[temp] + + +class BasicConfig(object): + logger_register_table = {} + basic_configure = { + 'level': Level.WARN, + 'debug': True, + 'stream': sys.stdout + } + + @classmethod + def getLogger(cls, name): + if name not in cls.logger_register_table: + logger = Logger(name) + cls.logger_register_table[name] = logger + else: + logger = cls.logger_register_table[name] + return logger + + @classmethod + def update(cls, **kwargs): + level = kwargs.pop('level', None) + if level is not None: + kwargs['level'] = getNameLevel(level) + return cls.basic_configure.update(kwargs) + + @classmethod + def get(cls, key): + return cls.basic_configure[key] + + @classmethod + def set(cls, key, value): + if key == 'level': + value = getNameLevel(value) + cls.basic_configure[key] = value + + +class Logger(object): + lock = _thread.allocate_lock() + + def __init__(self, name): + self.name = name + + @staticmethod + def __get_formatted_time(): + # (2023, 9, 30, 11, 11, 41, 5, 273) + cur_time_tuple = utime.localtime() + return '{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format( + cur_time_tuple[0], + cur_time_tuple[1], + cur_time_tuple[2], + cur_time_tuple[3], + cur_time_tuple[4], + cur_time_tuple[5] + ) + + def log(self, level, *message): + if not BasicConfig.get('debug'): + if level < BasicConfig.get('level'): + return + stream = BasicConfig.get('stream') + prefix = '[{}][{}][{}]'.format( + self.__get_formatted_time(), + getLevelName(level), + self.name, + ) + with self.lock: + print(prefix, *message, file=stream) + if isinstance(stream, io.TextIOWrapper): + stream.flush() + + def debug(self, *message): + self.log(Level.DEBUG, *message) + + def info(self, *message): + self.log(Level.INFO, *message) + + def warn(self, *message): + self.log(Level.WARN, *message) + + def error(self, *message): + self.log(Level.ERROR, *message) + + def critical(self, *message): + self.log(Level.CRITICAL, *message) + + +def getLogger(name): + return BasicConfig.getLogger(name) diff --git a/EG912U-GL-Tracker/code/libs/pypubsub.py b/EG912U-GL-Tracker/code/libs/pypubsub.py new file mode 100644 index 0000000..d4585b5 --- /dev/null +++ b/EG912U-GL-Tracker/code/libs/pypubsub.py @@ -0,0 +1,74 @@ +"""基于 QuecPython 的订阅/发布机制""" + + +from usr.libs.threading import Thread, Queue, Lock + + +class Publisher(object): + + def __init__(self): + self.__q = Queue() + self.__topic_manager_lock = Lock() + self.__topic_manager = {} + self.__listen_thread = Thread(target=self.__listen_worker) + + def listen(self): + self.__listen_thread.start() + + def __listen_worker(self): + while True: + topic, messages = self.__q.get() + # print("topic: {}, messages: {}".format(topic, messages)) + with self.__topic_manager_lock: + for listener in self.__topic_manager.setdefault(topic, []): + try: + listener(**messages) + except Exception as e: + print("listener error:", str(e)) + + def publish(self, topic, **kwargs): + self.__q.put((topic, kwargs)) + + def subscribe(self, topic, listener): + with self.__topic_manager_lock: + listener_list = self.__topic_manager.setdefault(topic, []) + listener_list.append(listener) + + def unsubscribe(self, topic, listener): + with self.__topic_manager_lock: + listener_list = self.__topic_manager.setdefault(topic, []) + try: + listener_list.remove(listener) + except ValueError: + pass + + +# global publisher +__publisher__ = None + + +def get_default_publisher(): + global __publisher__ + if __publisher__ is None: + __publisher__ = Publisher() + __publisher__.listen() + return __publisher__ + + + +def publish(topic, **kwargs): + """订阅消息""" + pub = get_default_publisher() + pub.publish(topic, **kwargs) + + +def subscribe(topic, listener): + """订阅消息""" + pub = get_default_publisher() + pub.subscribe(topic, listener) + + +def unsubscribe(topic, listener): + """取消订阅消息""" + pub = get_default_publisher() + pub.unsubscribe(topic, listener) diff --git a/EG912U-GL-Tracker/code/libs/threading.py b/EG912U-GL-Tracker/code/libs/threading.py new file mode 100644 index 0000000..5a8a327 --- /dev/null +++ b/EG912U-GL-Tracker/code/libs/threading.py @@ -0,0 +1,569 @@ +import utime +import sys +import _thread +import osTimer + + +class Lock(object): + + def __init__(self): + self.__lock = _thread.allocate_lock() + self.__owner = None + + def __enter__(self): + self.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.release() + + def acquire(self): + flag = self.__lock.acquire() + self.__owner = _thread.get_ident() + return flag + + def release(self): + self.__owner = None + return self.__lock.release() + + def locked(self): + return self.__lock.locked() + + @property + def owner(self): + return self.__owner + + +class _Waiter(object): + """WARNING: Waiter object can only be used once.""" + + def __init__(self): + self.__lock = Lock() + self.__lock.acquire() + self.__gotit = True + + @property + def __unlock_timer(self): + timer = getattr(self, '__unlock_timer__', None) + if timer is None: + timer = osTimer() + setattr(self, '__unlock_timer__', timer) + return timer + + @property + def __timer_lock(self): + lock = getattr(self, '__timer_lock__', None) + if lock is None: + lock = Lock() + setattr(self, '__timer_lock__', lock) + return lock + + def __auto_release(self, _): + with self.__timer_lock: + self.__gotit = not self.__release() + + def acquire(self, timeout=None): + if timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + gotit = self.__gotit + if timeout: + with self.__timer_lock: + self.__unlock_timer.start(timeout * 1000, 0, self.__auto_release) + self.__lock.acquire() # block here + if timeout: + with self.__timer_lock: + gotit = self.__gotit + self.__unlock_timer.stop() + return gotit + + def __release(self): + try: + self.__lock.release() + except RuntimeError: + return False + return True + + def release(self): + return self.__release() + + +class Condition(object): + + def __init__(self, lock=None): + if lock is None: + lock = Lock() + self.__lock = lock + self.__waiters = [] + self.acquire = self.__lock.acquire + self.release = self.__lock.release + + def __enter__(self): + self.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.release() + + def __is_owned(self): + return self.__lock.locked() and self.__lock.owner == _thread.get_ident() + + def wait(self, timeout=None): + if not self.__is_owned(): + raise RuntimeError('cannot wait on un-acquired lock.') + waiter = _Waiter() + self.__waiters.append(waiter) + self.release() + gotit = False + try: + gotit = waiter.acquire(timeout) + return gotit + finally: + self.acquire() + if not gotit: + try: + self.__waiters.remove(waiter) + except ValueError: + pass + + def wait_for(self, predicate, timeout=None): + endtime = None + remaining = timeout + result = predicate() + while not result: + if remaining is not None: + if endtime is None: + endtime = utime.time() + remaining + else: + remaining = endtime - utime.time() + if remaining <= 0.0: + break + self.wait(remaining) + result = predicate() + return result + + def notify(self, n=1): + if not self.__is_owned(): + raise RuntimeError('cannot wait on un-acquired lock.') + if n < 0: + raise ValueError('invalid param, n should be >= 0.') + waiters_to_notify = self.__waiters[:n] + for waiter in waiters_to_notify: + waiter.release() + try: + self.__waiters.remove(waiter) + except ValueError: + pass + + def notify_all(self): + self.notify(n=len(self.__waiters)) + + +class Event(object): + + def __init__(self): + self.__flag = False + self.__cond = Condition() + + def wait(self, timeout=None, clear=False): + with self.__cond: + result = self.__cond.wait_for(lambda: self.__flag, timeout=timeout) + if result and clear: + self.__flag = False + return result + + def set(self): + with self.__cond: + self.__flag = True + self.__cond.notify_all() + + def clear(self): + with self.__cond: + self.__flag = False + + def is_set(self): + with self.__cond: + return self.__flag + + +class EventSet(object): + + def __init__(self): + self.__set = 0 + self.__cond = Condition() + + def wait(self, event_set, timeout=None, clear=False): + with self.__cond: + result = self.__cond.wait_for(lambda: (event_set & self.__set) == event_set, timeout=timeout) + if result and clear: + self.__set &= ~event_set + return result + + def wait_any(self, event_set, timeout=None, clear=False): + with self.__cond: + result = self.__cond.wait_for(lambda: event_set & self.__set, timeout=timeout) + if result and clear: + self.__set &= ~event_set + return result + + def set(self, event_set): + with self.__cond: + self.__set |= event_set + self.__cond.notify_all() + + def clear(self, event_set): + with self.__cond: + self.__set &= ~event_set + + def is_set(self, event_set): + with self.__cond: + return (self.__set & event_set) == event_set + + def is_set_any(self, event_set): + with self.__cond: + return self.__set & event_set + + +class Semaphore(object): + + def __init__(self, value=1): + if value < 0: + raise ValueError("semaphore initial value must be >= 0") + self.__value = value + self.__cond = Condition() + + def __enter__(self): + self.acquire() + return self + + def __exit__(self, *args, **kwargs): + self.release() + + def counts(self): + with self.__cond: + return self.__value + + def acquire(self, block=True, timeout=None): + with self.__cond: + if not block: + if self.__value > 0: + self.__value -= 1 + return True + else: + return False + elif timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + else: + if self.__cond.wait_for(lambda: self.__value > 0, timeout=timeout): + self.__value -= 1 + return True + else: + return False + + def release(self, n=1): + if n < 1: + raise ValueError('n must be one or more') + with self.__cond: + self.__value += n + self.__cond.notify(n) + + def clear(self): + with self.__cond: + self.__value = 0 + + +class BoundedSemaphore(Semaphore): + + def __init__(self, value=1): + super().__init__(value) + self.__initial_value = value + + def release(self, n=1): + if n < 1: + raise ValueError('n must be one or more') + with self.__cond: + if self.__value + n > self.__initial_value: + raise ValueError("Semaphore released too many times") + self.__value += n + self.__cond.notify(n) + + +class Queue(object): + class Full(Exception): + pass + + class Empty(Exception): + pass + + def __init__(self, max_size=100): + self.queue = [] + self.__max_size = max_size + self.__lock = Lock() + self.__not_empty = Condition(self.__lock) + self.__not_full = Condition(self.__lock) + + def _put(self, item): + self.queue.append(item) + + def put(self, item, block=True, timeout=None): + with self.__not_full: + if not block: + if len(self.queue) >= self.__max_size: + raise self.Full + elif timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + else: + if not self.__not_full.wait_for(lambda: len(self.queue) < self.__max_size, timeout=timeout): + raise self.Full + self._put(item) + self.__not_empty.notify() + + def _get(self): + return self.queue.pop(0) + + def get(self, block=True, timeout=None): + with self.__not_empty: + if not block: + if len(self.queue) == 0: + raise self.Empty + elif timeout is not None and timeout <= 0: + raise ValueError("'timeout' must be a positive number.") + else: + if not self.__not_empty.wait_for(lambda: len(self.queue) != 0, timeout=timeout): + raise self.Empty + item = self._get() + self.__not_full.notify() + return item + + def size(self): + with self.__lock: + return len(self.queue) + + def clear(self): + with self.__lock: + self.queue.clear() + + +class LifoQueue(Queue): + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() + + +class PriorityQueue(Queue): + + @classmethod + def __siftdown(cls, heap, startpos, pos): + newitem = heap[pos] + while pos > startpos: + parentpos = (pos - 1) >> 1 + parent = heap[parentpos] + if newitem < parent: + heap[pos] = parent + pos = parentpos + continue + break + heap[pos] = newitem + + def _put(self, item): + self.queue.append(item) + self.__siftdown(self.queue, 0, len(self.queue) - 1) + + @classmethod + def __siftup(cls, heap, pos): + endpos = len(heap) + startpos = pos + newitem = heap[pos] + childpos = 2 * pos + 1 + while childpos < endpos: + rightpos = childpos + 1 + if rightpos < endpos and not heap[childpos] < heap[rightpos]: + childpos = rightpos + heap[pos] = heap[childpos] + pos = childpos + childpos = 2 * pos + 1 + heap[pos] = newitem + cls.__siftdown(heap, startpos, pos) + + def _get(self): + lastelt = self.queue.pop() + if self.queue: + returnitem = self.queue[0] + self.queue[0] = lastelt + self.__siftup(self.queue, 0) + return returnitem + return lastelt + + +class Thread(object): + DEFAULT_STACK_SIZE = _thread.stack_size() + + def __init__(self, target=None, args=(), kwargs=None): + self.__target = target + self.__args = args + self.__kwargs = kwargs or {} + self.__ident = None + self.__stopped_event = Event() + + def is_running(self): + if self.__ident is None: + return False + else: + return _thread.threadIsRunning(self.__ident) + + def join(self, timeout=None): + return self.__stopped_event.wait(timeout=timeout) + + def terminate(self): + """WARNING: you must release all resources after terminate thread, especially **Lock(s)**""" + if self.is_running(): + _thread.stop_thread(self.ident) + self.__ident = None + self.__stopped_event.set() + + def start(self, stack_size=None): + if self.__ident is not None: + raise RuntimeError("threads can only be started once") + if stack_size is not None: + _thread.stack_size(stack_size * 1024) + self.__ident = _thread.start_new_thread(self.__bootstrap, ()) + if stack_size is not None: + _thread.stack_size(self.DEFAULT_STACK_SIZE) + + def __bootstrap(self): + try: + self.run() + except Exception as e: + sys.print_exception(e) + finally: + self.__stopped_event.set() + + def run(self): + if self.__target: + self.__target(*self.__args, **self.__kwargs) + + @property + def ident(self): + return self.__ident + + +class _Result(object): + class TimeoutError(Exception): + pass + + class NotReadyError(Exception): + pass + + def __init__(self): + self.__rv = None + self.__exc = None + self.__finished = Event() + + def set(self, exc=None, rv=None): + self.__exc = exc + self.__rv = rv + self.__finished.set() + + def __get_value_or_raise_exc(self): + if self.__exc: + raise self.__exc + return self.__rv + + def get(self, block=True, timeout=None): + if not block: + if self.__finished.is_set(): + return self.__get_value_or_raise_exc() + raise self.NotReadyError('result not ready') + if self.__finished.wait(timeout=timeout): + return self.__get_value_or_raise_exc() + else: + raise self.TimeoutError('get result timeout.') + + +class AsyncTask(object): + + def __init__(self, target=None, args=(), kwargs=None): + self.__target = target + self.__args = args + self.__kwargs = kwargs or {} + + def delay(self, seconds=None): + result = _Result() + Thread(target=self.__run, args=(result, seconds)).start() + return result + + def __run(self, result, delay_seconds): + if delay_seconds is not None and delay_seconds > 0: + utime.sleep(delay_seconds) + try: + rv = self.__target(*self.__args, **self.__kwargs) + except Exception as e: + sys.print_exception(e) + result.set(exc=e) + else: + result.set(rv=rv) + + @classmethod + def wrapper(cls, func): + def inner_wrapper(*args, **kwargs): + return cls(target=func, args=args, kwargs=kwargs) + return inner_wrapper + + +class _WorkItem(object): + + def __init__(self, target=None, args=(), kwargs=None): + self.__target = target + self.__args = args + self.__kwargs = kwargs or {} + self.result = _Result() + + def __call__(self, *args, **kwargs): + try: + rv = self.__target(*self.__args, **self.__kwargs) + except Exception as e: + self.result.set(exc=e) + else: + self.result.set(rv=rv) + + +def _worker(work_queue): + while True: + try: + task = work_queue.get() + task() + except Exception as e: + sys.print_exception(e) + + +class ThreadPoolExecutor(object): + + def __init__(self, max_workers=4): + if max_workers <= 0: + raise ValueError('max_workers must be greater than 0.') + self.__max_workers = max_workers + self.__work_queue = Queue() + self.__threads = set() + self.__lock = Lock() + + def submit(self, *args, **kwargs): + with self.__lock: + item = _WorkItem(*args, **kwargs) + self.__work_queue.put(item) + self.__adjust_thread_count() + return item.result + + def __adjust_thread_count(self): + if len(self.__threads) < self.__max_workers: + t = Thread(target=_worker, args=(self.__work_queue,)) + t.start() + self.__threads.add(t) + + def shutdown(self): + with self.__lock: + for t in self.__threads: + t.terminate() + self.__threads = set() + self.__work_queue = Queue() diff --git a/EG912U-GL-Tracker/code/main.py b/EG912U-GL-Tracker/code/main.py new file mode 100644 index 0000000..609c484 --- /dev/null +++ b/EG912U-GL-Tracker/code/main.py @@ -0,0 +1,65 @@ +import utime +import dataCall +from machine import Pin +from usr.libs import Application +from usr.libs.logging import getLogger +from usr.extensions import ( + qth_client, + gnss_service, + lbs_service, + sensor_service, +) + +WAIT_NETWORK_READY_S = 30 # 30s + + +logger = getLogger(__name__) + +gpio = Pin(Pin.GPIO22, Pin.OUT, Pin.PULL_DISABLE, 1) # 拉高 P37 + +def create_app(name="SimpliKit", version="1.0.0", config_path="/usr/config.json"): + _app = Application(name, version) + _app.config.init(config_path) + + qth_client.init_app(_app) + gnss_service.init_app(_app) + lbs_service.init_app(_app) + sensor_service.init_app(_app) + + return _app + + +def wait_network_ready(): + wait_cnt = WAIT_NETWORK_READY_S / 5 + is_ready = False + + while wait_cnt: + lte = dataCall.getInfo(1, 0) + if lte[2][0] == 1: + is_ready = True + break + + utime.sleep(5) + wait_cnt -= 1 + + return is_ready + + +if __name__ == "__main__": + while True: + if wait_network_ready(): + logger.debug('lte network normal') + break + + logger.debug('wait lte network normal...') + ret=dataCall.setPDPContext(1, 0, 'BICSAPN', '', '', 0) + ret2=dataCall.activate(1) + while not ret and ret2: + ret=dataCall.setPDPContext(1, 0, 'BICSAPN', '', '', 0) # 激活之前,应该先配置APN,这里配置第1路的APN + ret2=dataCall.activate(1) + if ret and ret2: + print("Net injection failure") + break + + app = create_app() + app.run() diff --git a/QPY_OCPU_BETA0001_EC200U_EUAA_FW/QPY_OCPU_BETA0001_EC200U_EUAA_FW.pac b/EG912U-GL-Tracker/firmware/EG912UGLAAR05A01M08_TEST0220.pac similarity index 50% rename from QPY_OCPU_BETA0001_EC200U_EUAA_FW/QPY_OCPU_BETA0001_EC200U_EUAA_FW.pac rename to EG912U-GL-Tracker/firmware/EG912UGLAAR05A01M08_TEST0220.pac index 059780f..1e4ab00 100644 Binary files a/QPY_OCPU_BETA0001_EC200U_EUAA_FW/QPY_OCPU_BETA0001_EC200U_EUAA_FW.pac and b/EG912U-GL-Tracker/firmware/EG912UGLAAR05A01M08_TEST0220.pac differ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index aedae76..0000000 --- a/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -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. \ No newline at end of file diff --git a/QPY_OCPU_BETA0001_EC200U_EUAA_FW/Quectel_Disclaimer_for_Software_BETA_Version.pdf b/QPY_OCPU_BETA0001_EC200U_EUAA_FW/Quectel_Disclaimer_for_Software_BETA_Version.pdf deleted file mode 100644 index d1b1b0c..0000000 Binary files a/QPY_OCPU_BETA0001_EC200U_EUAA_FW/Quectel_Disclaimer_for_Software_BETA_Version.pdf and /dev/null differ diff --git a/QPY_OCPU_BETA0001_EC200U_EUAA_FW/changelog - EC200UEU_AA_BETA.md b/QPY_OCPU_BETA0001_EC200U_EUAA_FW/changelog - EC200UEU_AA_BETA.md deleted file mode 100644 index 9eaa002..0000000 --- a/QPY_OCPU_BETA0001_EC200U_EUAA_FW/changelog - EC200UEU_AA_BETA.md +++ /dev/null @@ -1,88 +0,0 @@ -## Release History -**[QPY_OCPU_EC200UEU_AA_BETA] 2024-04-18** -* ZH -* 支持功能list -1. app_fota -2. PWM -3. dataCall -4. fota -5. log -6. net -7. ntptime -8. pm -9. queue -10. sim -11. sys_bus -12. uio -13. ujson -14. BLE -15. usocket -16. utime -17. _thread -18. Timer -19. RTC -20. WDT -21. Pin -22. ExtInt -23. UART -24. SPI -25. IIC -26. Key -27. Power -28. ADC -29. PowerKey -30. TencentYun -31. voicecall -32. wifiscan -33. wifilocator -34. SMS -35. Audio -36. Ethernet -37. AliYun -38. GNSS -39. USSD - - - - -* EN -* Support function list -1. app_fota -2. PWM -3. dataCall -4. fota -5. log -6. net -7. ntptime -8. pm -9. queue -10. sim -11. sys_bus -12. uio -13. ujson -14. BLE -15. usocket -16. utime -17. _thread -18. Timer -19. RTC -20. WDT -21. Pin -22. ExtInt -23. UART -24. SPI -25. IIC -26. Key -27. Power -28. ADC -29. PowerKey -30. TencentYun -31. voicecall -32. wifiscan -33. wifilocator -34. SMS -35. Audio -36. Ethernet -37. AliYun -38. GNSS -39. USSD \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 3afd2e8..0000000 --- a/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# Smart Tracker Solution for QuecPython - -[中文](README.zh.md) | English - -Welcome to the QuecPython Tracker Solution repository! This repository provides a comprehensive solution for developing Tracker device applications using QuecPython. - -## Table of Contents - -- [Introduction](#introduction) -- [Functions](#Functions) -- [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Running the Application](#running-the-application) -- [Directory Structure](#directory-structure) -- [Contributing](#contributing) -- [License](#license) -- [Support](#support) - -## Introduction - -### Overview - -- Smart tracker -- Terminal device functions meet the majority of requirements in tracker application scenarios -- The visual operation platform and the mobile APP make device management and data viewing more convenient. - -![](./docs/en/media/tracker_process.png) - -### Features - -- Intelligent perception, recognition, and reporting of location information and danger alarms. -- Support integration with various IoT platforms such as Alibaba IoT Platform, ThingsBoard, and other private services. -- Secondary development with QuecPython to formulate modular and customizable solutions, thus shortening development cycles. -- Visual operation platform and mobile APP to control terminal devices. - -### Applications - -- Vehicle tracking -- Logistics and transportation -- People tracking -- Electronic student ID card -- Pet tracking -- Special industries (agricultural irrigation, rare species monitoring, etc.) - -![](./docs/en/media/tracker_application.png) - -## Functions - -- Multi-technology positioning, geo-fence alarm, danger alarm, SOS alarm reporting, audio monitoring, recording, historical track playback, remote control, etc. -- Smart positioning - - The system utilizes 4G communication/multi-technology positioning/distributed services to provide a one-stop solution from end to service for the smart tracker industry. -- All-platform support - - The device operation platform and mobile APP have all-round functions, enabling terminal device manufacturers to quickly manage devices and end users without the need to build your own service platforms. -- Reliable and stable - - The terminal device has high positioning accuracy, high sensitivity to danger perception, low power consumption, and stable operation. Terminal device manufacturers can develop customized solutions directly based on the public version, greatly shortening the hardware development cycle. - -![](./docs/en/media/tracker_funcion.png) - -## Getting Started - -### Prerequisites - -Before you begin, ensure you have the following prerequisites: - -- **Hardware**: - - - A set of EC200UEUAA QuecPython standard development board with LTE antenna Type-C data cable, etc - - > Click for Tracker EVB's [schematic](https://python.quectel.com/en/wp-content/uploads/sites/2/2024/11/EC200U_A_C4-P01-Series-EVB_SCH.pdf) and [silk screen](https://python.quectel.com/en/wp-content/uploads/sites/2/2024/11/EC200U_A_C4-P01-Series-EVB_Silkscreen.pdf) documents. - - - PC (Windows 7, Windows 10, or Windows 11) - - - One GNSS antenna - - - A jumper cap - - - A functional Nano SIM card - -- **Software**: - - USB driver for the QuecPython module: [QuecPython_USB_Driver_Win10_U_G](https://python.quectel.com/wp-content/uploads/2024/09/Quectel_Windows_USB_DriverU_V1.0.19.zip) - - debugging tool: [QPYcom](https://python.quectel.com/en/wp-content/uploads/sites/2/2024/11/QPYcom_V3.6.0.zip) - - QuecPython firmware and related software resources. - - Python text editor (e.g., [VSCode](https://code.visualstudio.com/), [Pycharm](https://www.jetbrains.com/pycharm/download/)). - -### Installation - -1. **Clone the Repository**: - - ```bash - # 1.Pull the main project code - git clone https://github.com/QuecPython/solution-tracker.git - - # 2.Enter the project - cd solution-tracker/ - - # 3.Checkout to master - git checkout master - - # 4.Sub project init - git submodule init - - # 5.Sub project code pull - git submodule update - - # 6.Enter the sub project - cd code/modules/ - - # 7.checkout to master - git checkout master - ``` - -2. **Flash the Firmware**: - Follow the [instructions](https://python.quectel.com/doc/Application_guide/en/dev-tools/QPYcom/qpycom-dw.html#Download-Firmware) to flash the firmware to the development board. - -### Running the Application - -1. **Connect the Hardware**: - Connect the hardware according to the following diagram: - ![](./docs/en/media/connect.png) - 1. Connect the antenna to the antenna connector marked with the word `LTE`. - 2. Connect the GNSS antenna to the antenna connector labeled with the word `GNSS`. - 3. Insert a usable Nano SIM card into the back of the development board at the position shown in the diagram. - 4. Use jumper caps to short circuit the two pins labeled with the word `GNSS-EN`, enabling the development board to have built-in GNSS functionality. - 5. Connect the development board and computer using a Type-C data cable. - -2. **Download Code to the Device**: - - Launch the QPYcom debugging tool. - - Connect the data cable to the computer. - - Follow the [instructions](https://python.quectel.com/doc/Application_guide/en/dev-tools/QPYcom/qpycom-dw.html#Download-Script) to import all files within the `code` folder into the module's file system, preserving the directory structure. - -3. **Run the Application**: - - Select the `File` tab. - - Select the `_main.py` script. - - Right-click and select `Run` or use the run shortcut button to execute the script. - -## Directory Structure - -```plaintext -solution-tracker/ -├── code/ -│   ├── modules/ -│   │   ├── docs/ -│   │   │   ├── en/ -│   │   │   └── zh/ -│   │   ├── aliIot.py -│   │   ├── battery.py -│   │   ├── battery.py -│   │   ├── buzzer.py -│   │   ├── common.py -│   │   ├── history.py -│   │   ├── led.py -│   │   ├── location.py -│   │   ├── logging.py -│   │   ├── net_manage.py -│   │   ├── player.py -│   │   ├── power_manage.py -│   │   ├── serial.py -│   │   ├── temp_humidity_sensor.py -│   │   ├── thingsboard.py -│   │   └── thingsboard.py -│   ├── _main.py -│   ├── settings.py -│   ├── settings_loc.py.py -│   ├── settings_server.py -│   ├── settings_user.py -│   ├── tracker_ali.py -│   └── tracker_tb.py -├── docs/ -│   ├── en/ -│   │   └── media/ -│   └── zh/ -│   └── media/ -├── object_model_demo/ -│   └── ali_cloud_object_model.json -├── QPY_OCPU_BETA0001_EC200U_EUAA_FW/ -│   └── QPY_OCPU_BETA0001_EC200U_EUAA_FW.pac -├── .gitignore -├── .gitmodules -├── CHANGELOG.md -├── LICENSE -├── readme.md -└── readme_zh.md -``` - -## Contributing - -We welcome contributions to improve this project! Please follow these steps to contribute: - -1. Fork the repository. -2. Create a new branch (`git checkout -b feature/your-feature`). -3. Commit your changes (`git commit -m 'Add your feature'`). -4. Push to the branch (`git push origin feature/your-feature`). -5. Open a Pull Request. - -## License - -This project is licensed under the Apache License. See the [LICENSE](LICENSE) file for details. - -## Support - -If you have any questions or need support, please refer to the [QuecPython documentation](https://python.quectel.com/doc/en) or open an issue in this repository. diff --git a/README.zh.md b/README.zh.md deleted file mode 100644 index ccf9be1..0000000 --- a/README.zh.md +++ /dev/null @@ -1,205 +0,0 @@ -# QuecPython 智能定位器解决方案 - -中文 | [English](README.md) - -欢迎来到 QuecPython Tracker 解决方案仓库!本仓库提供了一个全面的解决方案,用于使用 QuecPython Tracker 设备应用程序。 - -## 目录 - -- [介绍](#介绍) -- [功能](#功能) -- [快速开始](#快速开始) - - [先决条件](#先决条件) - - [安装](#安装) - - [运行应用程序](#运行应用程序) -- [目录结构](#目录结构) -- [贡献](#贡献) -- [许可证](#许可证) -- [支持](#支持) - -## 介绍 - -### 产品概述 - -- Tracker 智能定位器 -- 终端设备功能涵盖绝大部分定位器应用场景 -- 可视化运营平台+手机 APP,设备管理和数据查看更方便 - -![](./docs/zh/media/tracker_process.png) - -### 产品特点 - -- 位置信息、危险警情智能感知、识别和上报 -- 支持阿里 IoT 平台、ThingsBoard、私有服务等多种 IoT 平台对接 -- QuecPython 二次开发,模块化、定制化、缩短开发周期 -- 可视化运营平台、手机 APP 控制终端 - -### 应用行业 - -- 车载定位 -- 物流货运 -- 人员定位 -- 电子学生证 -- 宠物定位 -- 特殊行业(农业灌溉, 稀有物种监控等) - -![](./docs/zh/media/tracker_application.png) - -## 功能 - -- 多重定位、安全围栏、危险报警、紧急求救、语音监听、录音、轨迹回放、远程控制等 -- 智能定位 - - 系统利用 4G 通信/多重定位/分布式服务等技术,为智能定位器行业提供从端到服务的一站式解决方案 -- 全平台支持 - - 设备运营平台和手机 APP 功能齐全,终端设备厂商无需自行搭建服务平台即可快速实现对设备和终端用户的管理 -- 可靠稳定 - - 终端设备定位精度高、危险感知灵敏度高、功耗低、运行稳定,终端设备厂商可套壳即用,极大缩短硬件开发周期 - -![](./docs/zh/media/tracker_funcion.png) - -## 快速开始 - -### 先决条件 - -在开始之前,请确保您具备以下先决条件: - -- **硬件:** - - - Windows 电脑一台,建议 `Win10` 系统 - - - 一套 [EC200UEUAA QuecPython 标准开发板](https://python.quectel.com/doc/Getting_started/zh/evb/ec200x-evb.html)(含 LTE 天线、 Type-C 数据线等) - - > 点击查看 Tracker 定位器开发板的[原理图](https://python.quectel.com/wp-content/uploads/2024/09/EC200UA_C4-P01%E7%B3%BB%E5%88%97%E5%BC%80%E5%8F%91%E6%9D%BF%E5%8E%9F%E7%90%86%E5%9B%BE.pdf)和[丝印图](https://python.quectel.com/wp-content/uploads/2024/09/EC200UA_C4-P01%E7%B3%BB%E5%88%97%E5%BC%80%E5%8F%91%E6%9D%BF%E4%B8%9D%E5%8D%B0.pdf)文档。 - - - 一根 [GNSS天线](https://e.tb.cn/h.TpAFyEz02BnCHRD?tk=fznae6ITVEX) - - - 一个排针跳线帽 - - - 一张可正常使用的 Nano SIM 卡 - -- **软件:** - - - QuecPython 模块的 USB 驱动:[QuecPython_USB_Driver_Win10_U_G](https://python.quectel.com/wp-content/uploads/2024/09/Quectel_Windows_USB_DriverU_V1.0.19.zip) - - 调试工具 [QPYcom](https://images.quectel.com/python/2022/12/QPYcom_V3.6.0.zip) - - QuecPython 固件及相关软件资源 - - Python 文本编辑器(例如,[VSCode](https://code.visualstudio.com/)、[Pycharm](https://www.jetbrains.com/pycharm/download/)) - -### 安装 - -1. **克隆仓库**: - - ```bash - # 1.拉取主项目代码 - git clone https://github.com/QuecPython/solution-tracker.git - - # 2.进入项目根目录 - cd solution-tracker/ - - # 3.切换对应的主项目分支 - git checkout master - - # 4.子项目初始化 - git submodule init - - # 5.子项目代码拉取 - git submodule update - - # 6.进入子项目目录 - cd code/modules/ - - # 7.切换对应的子项目分支 - git checkout master - ``` - -2. **烧录固件:** - 按照[说明](https://python.quectel.com/doc/Application_guide/zh/dev-tools/QPYcom/qpycom-dw.html#%E4%B8%8B%E8%BD%BD%E5%9B%BA%E4%BB%B6)将固件烧录到开发板上。 - -### 运行应用程序 - -1. **连接硬件:** - 按照下图进行硬件连接: - - ![](./docs/zh/media/连线.png) - - 1. 将 LTE 天线连接至标识有 `LTE` 字样的天线连接座上 - 2. 将 GNSS 天线连接至标识有 `GNSS` 字样的天线连接座上 - 3. 在图示位置开发板背面插入可用的 Nano SIM 卡 - 4. 使用跳线帽将标识有 `GNSS_EN` 字样的两根排针短接,使能开发板内置 GNSS 功能 - 5. 使用 Type-C 数据线连接开发板和电脑 - -2. **将代码下载到设备:** - - 启动 QPYcom 调试工具。 - - 将数据线连接到计算机。 - - 按照[说明](https://python.quectel.com/doc/Application_guide/zh/dev-tools/QPYcom/qpycom-dw.html#%E4%B8%8B%E8%BD%BD%E8%84%9A%E6%9C%AC)将 `code` 文件夹中的所有文件导入到模块的文件系统中,保留目录结构。 - -3. **运行应用程序:** - - 选择 `File` 选项卡。 - - 选择 `_main.py` 脚本。 - - 右键单击并选择 `Run` 或使用`运行`快捷按钮执行脚本。 - -## 目录结构 - -```plaintext -solution-tracker/ -├── code/ -│   ├── modules/ -│   │   ├── docs/ -│   │   │   ├── en/ -│   │   │   └── zh/ -│   │   ├── aliIot.py -│   │   ├── battery.py -│   │   ├── battery.py -│   │   ├── buzzer.py -│   │   ├── common.py -│   │   ├── history.py -│   │   ├── led.py -│   │   ├── location.py -│   │   ├── logging.py -│   │   ├── net_manage.py -│   │   ├── player.py -│   │   ├── power_manage.py -│   │   ├── serial.py -│   │   ├── temp_humidity_sensor.py -│   │   ├── thingsboard.py -│   │   └── thingsboard.py -│   ├── _main.py -│   ├── settings.py -│   ├── settings_loc.py.py -│   ├── settings_server.py -│   ├── settings_user.py -│   ├── tracker_ali.py -│   └── tracker_tb.py -├── docs/ -│   ├── en/ -│   │   └── media/ -│   └── zh/ -│   └── media/ -├── object_model_demo/ -│   └── ali_cloud_object_model.json -├── QPY_OCPU_BETA0001_EC200U_EUAA_FW/ -│   └── QPY_OCPU_BETA0001_EC200U_EUAA_FW.pac -├── .gitignore -├── .gitmodules -├── CHANGELOG.md -├── LICENSE -├── readme.md -└── readme_zh.md -``` - -## 贡献 - -我们欢迎对本项目的改进做出贡献!请按照以下步骤进行贡献: - -1. Fork 此仓库。 -2. 创建一个新分支(`git checkout -b feature/your-feature`)。 -3. 提交您的更改(`git commit -m 'Add your feature'`)。 -4. 推送到分支(`git push origin feature/your-feature`)。 -5. 打开一个 Pull Request。 - -## 许可证 - -本项目使用 Apache 许可证。详细信息请参阅 [LICENSE](LICENSE) 文件。 - -## 支持 - -如果您有任何问题或需要支持,请参阅 [QuecPython 文档](https://python.quectel.com/doc)或在本仓库中打开一个 issue。 diff --git a/code/_main.py b/code/_main.py deleted file mode 100644 index bd63a9d..0000000 --- a/code/_main.py +++ /dev/null @@ -1,120 +0,0 @@ -# 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. - -""" -@file :_main.py -@author :Jack Sun (jack.sun@quectel.com) -@brief :Project start. -@version :2.2.0 -@date :2022-10-31 14:42:25 -@copyright :Copyright (c) 2022 -""" - -import _thread -try: - from modules.battery import Battery - from modules.history import History - from modules.logging import getLogger - from modules.net_manage import NetManager - from modules.thingsboard import TBDeviceMQTTClient - from modules.power_manage import PowerManage - from modules.aliIot import AliIot, AliIotOTA - from modules.location import GNSS, CellLocator, WiFiLocator, CoordinateSystemConvert - from settings_user import UserConfig - from tracker_tb import Tracker as TBTracker - from tracker_ali import Tracker as AliTracker - from settings import Settings, PROJECT_NAME, PROJECT_VERSION, FIRMWARE_NAME, FIRMWARE_VERSION -except ImportError: - from usr.modules.battery import Battery - from usr.modules.history import History - from usr.modules.logging import getLogger - from usr.modules.net_manage import NetManager - from usr.modules.thingsboard import TBDeviceMQTTClient - from usr.modules.power_manage import PowerManage - from usr.modules.aliIot import AliIot, AliIotOTA - from usr.modules.location import GNSS, CellLocator, WiFiLocator, CoordinateSystemConvert - from usr.settings_user import UserConfig - from usr.tracker_tb import Tracker as TBTracker - from usr.tracker_ali import Tracker as AliTracker - from usr.settings import Settings, PROJECT_NAME, PROJECT_VERSION, FIRMWARE_NAME, FIRMWARE_VERSION - -log = getLogger(__name__) - -def main(): - log.debug("[x] Main start.") - log.info("PROJECT_NAME: %s, PROJECT_VERSION: %s" % (PROJECT_NAME, PROJECT_VERSION)) - log.info("DEVICE_FIRMWARE_NAME: %s, DEVICE_FIRMWARE_VERSION: %s" % (FIRMWARE_NAME, FIRMWARE_VERSION)) - - # Init settings. - settings = Settings() - # Init battery. - battery = Battery() - # Init history - history = History() - # Init power manage and set device low energy. - power_manage = PowerManage() - power_manage.autosleep(1) - # Init net modules and start net connect. - net_manager = NetManager() - _thread.stack_size(0x1000) - _thread.start_new_thread(net_manager.net_connect, ()) - # Init GNSS modules and start reading and parsing gnss data. - loc_cfg = settings.read("loc") - gnss = GNSS(**loc_cfg["gps_cfg"]) - gnss.set_trans(0) - gnss.start() - # Init cell and wifi location modules. - cell = CellLocator(**loc_cfg["cell_cfg"]) - wifi = WiFiLocator(**loc_cfg["wifi_cfg"]) - cyc = CoordinateSystemConvert() - # Init tracker business modules. - user_cfg = settings.read("user") - server_cfg = settings.read("server") - # Init coordinate system convert modules. - if user_cfg["server"] == UserConfig._server.AliIot: - server = AliIot(**server_cfg) - server_ota = AliIotOTA(PROJECT_NAME, FIRMWARE_NAME) - server_ota.set_server(server) - tracker = AliTracker() - elif user_cfg["server"] == UserConfig._server.ThingsBoard: - # Init server modules. - server = TBDeviceMQTTClient(**server_cfg) - tracker = TBTracker() - else: - raise ValueError("User config server is not compared.") - tracker.add_module(settings) - tracker.add_module(battery) - tracker.add_module(history) - tracker.add_module(net_manager) - tracker.add_module(server) - tracker.add_module(server_ota) - tracker.add_module(gnss) - tracker.add_module(cell) - tracker.add_module(wifi) - tracker.add_module(cyc) - server.add_event("over_speed_alert") - server.add_event("sim_abnormal_alert") - server.add_event("low_power_alert") - server.add_event("fault_alert") - # Set net modules callback. - net_manager.set_callback(tracker.net_callback) - # Set server modules callback. - server.set_callback(tracker.server_callback) - # Start tracker business. - tracker.running() - - log.debug("[x] Main over.") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/code/modules b/code/modules deleted file mode 160000 index c2b6bae..0000000 --- a/code/modules +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c2b6baedf489fb5a5cdb598a1e185f55b0f0a3b2 diff --git a/code/settings.py b/code/settings.py deleted file mode 100644 index bd31426..0000000 --- a/code/settings.py +++ /dev/null @@ -1,91 +0,0 @@ -# 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. - -""" -@file :settings.py -@author :Jack Sun (jack.sun@quectel.com) -@brief :Project settings. -@version :2.2.0 -@date :2022-10-31 14:42:25 -@copyright :Copyright (c) 2022 -""" - -import uos -import ql_fs -import modem -import _thread -import usys as sys - -try: - from settings_server import AliIotConfig, ThingsBoardConfig - from settings_loc import LocConfig - from settings_user import UserConfig -except ImportError: - from usr.settings_server import AliIotConfig, ThingsBoardConfig - from usr.settings_loc import LocConfig - from usr.settings_user import UserConfig - -PROJECT_NAME = "QuecPython-Tracker" - -PROJECT_VERSION = "2.2.0" - -FIRMWARE_NAME = uos.uname()[0].split("=")[1] - -FIRMWARE_VERSION = modem.getDevFwVersion() - -class Settings: - - def __init__(self, config_file="/usr/tracker_config.json"): - self.__file = config_file - self.__lock = _thread.allocate_lock() - self.__data = {} - self.__init_config() - - def __init_config(self): - try: - if ql_fs.path_exists(self.__file): - ql_fs.touch(self.__file, {}) - - # UserConfig init - self.__data["user"] = {k: v for k, v in UserConfig.__dict__.items() if not k.startswith("_")} - self.__data["user"]["ota_status"]["sys_current_version"] = FIRMWARE_VERSION - self.__data["user"]["ota_status"]["app_current_version"] = PROJECT_VERSION - - # CloudConfig init - self.__data["server"] = {} - if self.__data["user"]["server"] == UserConfig._server.AliIot: - self.__data["server"] = {k: v for k, v in AliIotConfig.__dict__.items() if not k.startswith("_")} - elif self.__data["user"]["server"] == UserConfig._server.ThingsBoard: - self.__data["server"] = {k: v for k, v in ThingsBoardConfig.__dict__.items() if not k.startswith("_")} - - # LocConfig init - self.__data["loc"] = {k: v for k, v in LocConfig.__dict__.items() if not k.startswith("_")} - ql_fs.touch(self.__file, self.__data) - except Exception as e: - sys.print_exception(e) - - def read(self, key=None): - with self.__lock: - try: - return self.__data if key is None else self.__data.get(key) - except Exception as e: - sys.print_exception(e) - - def save(self, data): - with self.__lock: - res = -1 - if isinstance(data, dict): - self.__data.update(data) - res = ql_fs.touch(self.__file, self.__data) - return True if res == 0 else False diff --git a/code/settings_loc.py b/code/settings_loc.py deleted file mode 100644 index ced84be..0000000 --- a/code/settings_loc.py +++ /dev/null @@ -1,73 +0,0 @@ -# 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. - -""" -@file :dev_settings_loc.py -@author :Jack Sun (jack.sun@quectel.com) -@brief :Loction config. -@version :2.2.0 -@date :2022-10-31 14:42:25 -@copyright :Copyright (c) 2022 -""" - -from machine import UART - - -class LocConfig: - - class _gps_mode: - internal = 0x1 - external_uart = 0x2 - external_i2c = 0x3 - - class _map_coordinate_system: - WGS84 = "WGS84" - GCJ02 = "GCJ02" - - class _gps_sleep_mode: - none = 0x0 - pull_off = 0x1 - backup = 0x2 - standby = 0x3 - - profile_idx = 1 - - map_coordinate_system = _map_coordinate_system.GCJ02 - - gps_sleep_mode = _gps_sleep_mode.none - - gps_cfg = { - "gps_mode": _gps_mode.internal, - "UARTn": UART.UART2, - "buadrate": 115200, - "databits": 8, - "parity": 0, - "stopbits": 1, - "flowctl": 0, - "PowerPin": None, - "StandbyPin": None, - "BackupPin": None, - } - - cell_cfg = { - "serverAddr": "www.queclocator.com", - "port": 80, - "token": "xxxxxxxxxx", - "timeout": 3, - "profileIdx": profile_idx, - } - - wifi_cfg = { - "token": "xxxxxxxxxx" - } diff --git a/code/settings_server.py b/code/settings_server.py deleted file mode 100644 index 251644d..0000000 --- a/code/settings_server.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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. - -""" -@file :dev_settings_server.py -@author :Jack Sun (jack.sun@quectel.com) -@brief :Server config. -@version :2.2.0 -@date :2022-10-31 14:42:25 -@copyright :Copyright (c) 2022 -""" - - -class AliIotConfig: - - product_key = "" - device_name = "" - device_secret = "" - product_secret = None - server = "iot-as-mqtt.cn-shanghai.aliyuncs.com" - qos = 1 - - - -class ThingsBoardConfig: - - host = "" - port = 1883 - username = "" - qos = 0 - client_id = "" diff --git a/code/settings_user.py b/code/settings_user.py deleted file mode 100644 index 63e4ffb..0000000 --- a/code/settings_user.py +++ /dev/null @@ -1,119 +0,0 @@ -# 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. - -""" -@file :settings_user.py -@author :Jack Sun (jack.sun@quectel.com) -@brief :User setting config. -@version :2.2.0 -@date :2023-04-11 11:43:11 -@copyright :Copyright (c) 2022 -""" - - -class UserConfig: - - class _server: - none = 0x0 - AliIot = 0x1 - ThingsBoard = 0x2 - - class _loc_method: - none = 0x0 - gps = 0x1 - cell = 0x2 - wifi = 0x4 - all = 0x7 - - class _work_mode: - cycle = 0x1 - intelligent = 0x2 - - class _drive_behavior_code: - none = 0x0 - sharply_start = 0x1 - sharply_stop = 0x2 - sharply_turn_left = 0x3 - sharply_turn_right = 0x4 - - class _ota_upgrade_status: - none = 0x0 - to_be_updated = 0x1 - updating = 0x2 - update_successed = 0x3 - update_failed = 0x4 - - class _ota_upgrade_module: - none = 0x0 - sys = 0x1 - app = 0x2 - - debug = 1 - - log_level = "DEBUG" - - checknet_timeout = 60 - - server = _server.AliIot - - phone_num = "" - - low_power_alert_threshold = 20 - - low_power_shutdown_threshold = 5 - - over_speed_threshold = 50 - - sw_ota = 1 - - sw_ota_auto_upgrade = 1 - - sw_voice_listen = 0 - - sw_voice_record = 0 - - sw_fault_alert = 1 - - sw_low_power_alert = 1 - - sw_over_speed_alert = 1 - - sw_sim_abnormal_alert = 1 - - sw_disassemble_alert = 1 - - sw_drive_behavior_alert = 1 - - drive_behavior_code = _drive_behavior_code.none - - loc_method = _loc_method.gps - - loc_gps_read_timeout = 300 - - work_mode = _work_mode.cycle - - work_mode_timeline = 3600 - - work_cycle_period = 10 - - user_ota_action = -1 - - ota_status = { - "sys_current_version": "", - "sys_target_version": "--", - "app_current_version": "", - "app_target_version": "--", - "upgrade_module": _ota_upgrade_module.none, - "upgrade_status": _ota_upgrade_status.none, - } diff --git a/code/tracker_ali.py b/code/tracker_ali.py deleted file mode 100644 index 3edad60..0000000 --- a/code/tracker_ali.py +++ /dev/null @@ -1,458 +0,0 @@ -# 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. - -""" -@file :tracker_ali.py -@author :Jack Sun (jack.sun@quectel.com) -@brief :Tracker by aliyun. -@version :2.2.0 -@date :2023-04-11 11:43:11 -@copyright :Copyright (c) 2022 -""" - -import sys -import utime -import _thread -import osTimer -from misc import Power -from queue import Queue -from machine import RTC - -try: - from settings_user import UserConfig - from settings import Settings, PROJECT_NAME, PROJECT_VERSION, FIRMWARE_NAME, FIRMWARE_VERSION - from modules.battery import Battery - from modules.history import History - from modules.logging import getLogger - from modules.net_manage import NetManager - from modules.aliIot import AliIot, AliIotOTA - from modules.power_manage import PowerManage, PMLock - from modules.location import GNSS, GNSSBase, CellLocator, WiFiLocator, CoordinateSystemConvert -except ImportError: - from usr.settings_user import UserConfig - from usr.settings import Settings, PROJECT_NAME, PROJECT_VERSION, FIRMWARE_NAME, FIRMWARE_VERSION - from usr.modules.battery import Battery - from usr.modules.history import History - from usr.modules.logging import getLogger - from usr.modules.net_manage import NetManager - from usr.modules.aliIot import AliIot, AliIotOTA - from usr.modules.power_manage import PowerManage, PMLock - from usr.modules.location import GNSS, GNSSBase, CellLocator, WiFiLocator, CoordinateSystemConvert - -log = getLogger(__name__) - - -class Tracker: - - def __init__(self): - self.__server = None - self.__server_ota = None - self.__battery = None - self.__history = None - self.__gnss = None - self.__cell = None - self.__wifi = None - self.__csc = None - self.__net_manager = None - self.__pm = None - self.__settings = None - - self.__business_lock = PMLock("block") - self.__business_tid = None - self.__business_rtc = RTC() - self.__business_queue = Queue() - self.__business_tag = 0 - self.__running_tag = 0 - self.__server_ota_flag = 0 - self.__server_reconn_timer = osTimer() - self.__server_conn_tag = 0 - self.__server_reconn_count = 0 - self.__reset_tag = 0 - - def __business_start(self): - if not self.__business_tid: - _thread.stack_size(0x2000) - self.__business_tid = _thread.start_new_thread(self.__business_running, ()) - - def __business_stop(self): - if self.__business_tid and _thread.threadIsRunning(self.__business_tid): - try: - _thread.stop_thread(self.__business_tid) - except Exception as e: - sys.print_exception(e) - self.__business_tid = None - - def __business_running(self): - while True: - data = self.__business_queue.get() - with self.__business_lock: - self.__business_tag = 1 - if data[0] == 0: - if data[1] == "loc_report": - self.__loc_report() - elif data[1] == "server_connect": - self.__server_connect() - elif data[1] == "check_ota": - self.__server_check_ota() - elif data[1] == "ota_refresh": - self.__ota_cfg_refresh() - if data[0] == 1: - self.__server_option(*data[1]) - self.__business_tag = 0 - - def __loc_report(self): - his_data = {"properties": {}, "events": []} - loc_state, properties = self.__get_device_infos() - alarms = self.__get_alarms(properties) - res = False - if self.__server.status: - res = self.__server.properties_report(properties) - if not res: - his_data["properties"] = properties - for alarm in alarms: - res = self.__server.event_report(alarm, {}) - if not res: - his_data["events"].append(alarm) - - if loc_state and not self.__server.status: - his_data["properties"] = properties - his_data["events"] = alarms - - if his_data["properties"] or his_data["events"]: - self.__history.write([his_data]) - - self.__history_report() - - user_cfg = self.__settings.read("user") - self.__set_rtc(user_cfg["work_cycle_period"], self.loc_report) - - def __history_report(self): - failed_datas = [] - his_datas = self.__history.read() - if his_datas["data"]: - for item in his_datas["data"]: - faile_data = {"properties": {}, "events": []} - res = self.__server.properties_report(item["properties"]) - if not res: - faile_data["properties"] = item["properties"] - for alarm in item["events"]: - res = self.__server.event_report(alarm, {}) - if not res: - faile_data["events"].append(alarm) - if faile_data["properties"] or faile_data["events"]: - failed_datas.append(faile_data) - if failed_datas: - self.__history.write(failed_datas) - - def __get_device_infos(self): - user_cfg = self.__settings.read("user") - loc_cfg = self.__settings.read("loc") - properties = { - "power_switch": 1, - "energy": self.__battery.energy, - "voltage": self.__battery.voltage, - "local_time": str(utime.mktime(utime.localtime()) * 1000), - "loc_method": { - "gps": int((user_cfg["loc_method"] & UserConfig._loc_method.gps) / UserConfig._loc_method.gps), - "cell": int((user_cfg["loc_method"] & UserConfig._loc_method.cell) / UserConfig._loc_method.cell), - "wifi": int((user_cfg["loc_method"] & UserConfig._loc_method.wifi) / UserConfig._loc_method.wifi), - }, - "phone_num": user_cfg["phone_num"], - "work_mode": user_cfg["work_mode"], - "work_cycle_period": user_cfg["work_cycle_period"], - "low_power_alert_threshold": user_cfg["low_power_alert_threshold"], - "low_power_shutdown_threshold": user_cfg["low_power_shutdown_threshold"], - "sw_ota": user_cfg["sw_ota"], - "sw_ota_auto_upgrade": user_cfg["sw_ota_auto_upgrade"], - "sw_voice_listen": user_cfg["sw_voice_listen"], - "sw_voice_record": user_cfg["sw_voice_record"], - "sw_fault_alert": user_cfg["sw_fault_alert"], - "sw_low_power_alert": user_cfg["sw_low_power_alert"], - "sw_over_speed_alert": user_cfg["sw_over_speed_alert"], - "sw_sim_abnormal_alert": user_cfg["sw_sim_abnormal_alert"], - "sw_disassemble_alert": user_cfg["sw_disassemble_alert"], - "sw_drive_behavior_alert": user_cfg["sw_drive_behavior_alert"], - "drive_behavior_code": user_cfg["drive_behavior_code"], - "over_speed_threshold": user_cfg["over_speed_threshold"], - "user_ota_action": user_cfg["user_ota_action"], - "ota_status": user_cfg["ota_status"], - "work_mode_timeline": user_cfg["work_mode_timeline"], - "loc_gps_read_timeout": user_cfg["loc_gps_read_timeout"], - "gps_mode": loc_cfg["gps_cfg"]["gps_mode"], - "device_module_status": { - # "net": 0, - # "location": 0, - # "temp_sensor": 0, - # "light_sensor": 0, - # "move_sensor": 0, - # "mike": 0, - }, - } - loc_state, loc_data = self.__get_loc_data() - properties.update(loc_data) - properties["device_module_status"]["location"] = 1 if properties["GeoLocation"]["Longitude"] else 0 - properties["device_module_status"]["temp_sensor"] = 1 if properties.get("temperature") is not None or properties.get("humidity") is not None else 0 - properties["device_module_status"]["net"] = int(self.__net_manager.net_status()) - return (loc_state, properties) - - def __get_loc_data(self): - loc_state = 0 - loc_data = { - "GeoLocation": { - "Longitude": 0.0, - "Latitude": 0.0, - "Altitude": 0.0, - "CoordinateSystem": 1, - }, - "current_speed": 0, - } - loc_cfg = self.__settings.read("loc") - loc_data["GeoLocation"]["CoordinateSystem"] = 1 if loc_cfg["map_coordinate_system"] == "WGS84" else 2 - user_cfg = self.__settings.read("user") - if user_cfg["loc_method"] & UserConfig._loc_method.gps: - res = self.__gnss.read() - if res["state"] == "A": - loc_data["GeoLocation"]["Latitude"] = float(res["lat"]) * (1 if res["lat_dir"] == "N" else -1) - loc_data["GeoLocation"]["Longitude"] = float(res["lng"]) * (1 if res["lng_dir"] == "E" else -1) - loc_data["GeoLocation"]["Altitude"] = res["altitude"] - loc_data["current_speed"] = float(res["speed"]) - loc_state = 1 - if loc_state == 0 and user_cfg["loc_method"] & UserConfig._loc_method.cell: - res = self.__cell.read() - if res: - loc_data["GeoLocation"]["Longitude"] = res[0] - loc_data["GeoLocation"]["Latitude"] = res[1] - loc_state = 1 - if loc_state == 0 and user_cfg["loc_method"] & UserConfig._loc_method.wifi: - res = self.__wifi.read() - if res: - loc_data["GeoLocation"]["Longitude"] = res[0] - loc_data["GeoLocation"]["Latitude"] = res[1] - loc_state = 1 - if loc_state == 1 and loc_cfg["map_coordinate_system"] == "GCJ02": - lng, lat = self.__csc.wgs84_to_gcj02(loc_data["GeoLocation"]["Longitude"], loc_data["GeoLocation"]["Latitude"]) - loc_data["GeoLocation"]["Longitude"] = lng - loc_data["GeoLocation"]["Latitude"] = lat - return (loc_state, loc_data) - - def __get_alarms(self, properties): - alarms = [] - user_cfg = self.__settings.read("user") - if user_cfg["sw_over_speed_alert"] and properties["current_speed"] >= user_cfg["over_speed_threshold"]: - alarms.append("over_speed_alert") - if user_cfg["sw_sim_abnormal_alert"] and self.__net_manager.sim_status() != 1: - alarms.append("sim_abnormal_alert") - if user_cfg["sw_low_power_alert"] and properties["energy"] < user_cfg["low_power_alert_threshold"]: - alarms.append("low_power_alert") - if user_cfg["sw_fault_alert"] and 0 in properties["device_module_status"].values(): - alarms.append("fault_alert") - return alarms - - def __set_rtc(self, period, callback): - self.__business_rtc.enable_alarm(0) - if callback and callable(callback): - self.__business_rtc.register_callback(callback) - atime = utime.localtime(utime.mktime(utime.localtime()) + period) - alarm_time = (atime[0], atime[1], atime[2], atime[6], atime[3], atime[4], atime[5], 0) - _res = self.__business_rtc.set_alarm(alarm_time) - log.debug("alarm_time: %s, set_alarm res %s." % (str(alarm_time), _res)) - return self.__business_rtc.enable_alarm(1) if _res == 0 else -1 - - def __server_connect(self): - if self.__net_manager.net_status(): - # self.__server.disconnect() - self.__server.connect() - if not self.__server.status: - self.__server_reconn_timer.stop() - self.__server_reconn_timer.start(60 * 1000, 0, self.server_connect) - self.__server_reconn_count += 1 - else: - self.__server_reconn_count = 0 - - # When server not connect success after 20 miuntes, to reset device. - if self.__server_reconn_count >= 20: - _thread.stack_size(0x1000) - _thread.start_new_thread(self.__power_restart, ()) - self.__server_conn_tag = 0 - - def __server_cfg_save(self, data): - save_tag = 0 - server_cfg = self.__settings.read("server") - if server_cfg["product_key"] != data["product_key"]: - server_cfg["product_key"] = data["product_key"] - save_tag = 1 - if server_cfg["product_secret"] != data["product_secret"]: - server_cfg["product_secret"] = data["product_secret"] - save_tag = 1 - if server_cfg["device_name"] != data["device_name"]: - server_cfg["device_name"] = data["device_name"] - save_tag = 1 - if server_cfg["device_secret"] != data["device_secret"]: - server_cfg["device_secret"] = data["device_secret"] - save_tag = 1 - if save_tag == 1: - self.__settings.save({"server": server_cfg}) - - def __server_option(self, topic, data): - if topic.endswith("/property/set"): - self.__server_property_set(data) - elif topic.find("/rrpc/request/") != -1: - msg_id = topic.split("/")[-1] - self.__server_rrpc_response(msg_id, data) - elif topic.find("/thing/service/") != -1: - service = topic.split("/")[-1] - self.__server_service_response(service, data) - elif topic.startswith("/ota/device/upgrade/") or topic.endswith("/ota/firmware/get_reply"): - user_cfg = self.__settings.read("user") - if self.__server_ota_flag == 0: - if user_cfg["sw_ota"] == 1: - self.__server_ota_flag = 1 - if user_cfg["sw_ota_auto_upgrade"] == 1 or user_cfg["user_ota_action"] == 1: - self.__server_ota_process(data) - else: - self.__server_ota_flag = 0 - self.__server_ota.set_ota_data(data["data"]) - ota_info = self.__server_ota.get_ota_info() - ota_info["ota_status"] = 1 - self.__server_ota_state_save(**ota_info) - else: - module = data.get("data", {}).get("module") - self.__server.ota_device_progress(-1, "Device is not alowed ota.", module) - - def __server_property_set(self, data): - set_properties = data.get("params", {}) - user_cfg = self.__settings.read("user") - user_cfg.update(set_properties) - if self.__settings.save({"user": user_cfg}): - self.__server.property_set_reply(data.get("id"), 200, "success") - self.__business_queue.put((0, "loc_report")) - else: - self.__server.property_set_reply(data.get("id"), 9201, "save properties failed") - - def __server_ota_process(self, data): - code = data.get("code") - module = data.get("data", {}).get("module") - if code in ("1000", 200) and module: - self.__server.ota_device_progress(1, "", module) - self.__server_ota.set_ota_data(data["data"]) - ota_info = self.__server_ota.get_ota_info() - ota_info["ota_status"] = 2 - self.__server_ota_state_save(**ota_info) - if self.__server_ota.start(): - ota_info["ota_status"] = 3 - self.__server_ota_state_save(**ota_info) - self.__power_restart() - else: - ota_info["ota_status"] = 4 - self.__server_ota_state_save(**ota_info) - self.__server_ota_flag = 0 - - def __server_ota_state_save(self, ota_module, ota_version, ota_status): - user_cfg = self.__settings.read("user") - if ota_module == PROJECT_NAME: - user_cfg["ota_status"]["upgrade_module"] = 2 - user_cfg["ota_status"]["upgrade_status"] = ota_status - user_cfg["ota_status"]["app_target_version"] = ota_version - if ota_module == FIRMWARE_NAME: - user_cfg["ota_status"]["upgrade_module"] = 1 - user_cfg["ota_status"]["upgrade_status"] = ota_status - user_cfg["ota_status"]["sys_target_version"] = ota_version - self.__settings.save({"user": user_cfg}) - - def __server_check_ota(self): - if self.__server_ota_flag == 0 and self.__server.status: - res = self.__server.ota_device_inform(PROJECT_VERSION, PROJECT_NAME) - log.debug("ota_device_inform report project %s" % res) - res = self.__server.ota_device_inform(FIRMWARE_VERSION, FIRMWARE_NAME) - log.debug("ota_device_inform report firmware %s" % res) - res = self.__server.ota_firmware_get(PROJECT_NAME) - log.debug("ota_firmware_get project %s" % res) - res = self.__server.ota_firmware_get(FIRMWARE_NAME) - log.debug("ota_firmware_get firmware %s" % res) - - def __server_rrpc_response(self, msg_id, data): - self.__server.rrpc_response(msg_id, data) - - def __server_service_response(self, service, data): - msg_id = data.get("id") - self.__server.service_response(service, 200, {}, msg_id, "success") - - def __power_restart(self): - log.debug("__power_restart") - Power.powerRestart() - - def __ota_cfg_refresh(self): - user_cfg = self.__settings.read("user") - if user_cfg["ota_status"]["upgrade_status"] in (3, 4): - user_cfg["ota_status"]["upgrade_status"] = 0 - if user_cfg["ota_status"]["upgrade_module"] == 1: - user_cfg["ota_status"]["sys_target_version"] = "--" - if user_cfg["ota_status"]["upgrade_module"] == 2: - user_cfg["ota_status"]["app_target_version"] = "--" - user_cfg["ota_status"]["upgrade_module"] = 0 - user_cfg["user_ota_action"] = -1 - self.__settings.save({"user": user_cfg}) - - def add_module(self, module): - if isinstance(module, AliIot): - self.__server = module - elif isinstance(module, AliIotOTA): - self.__server_ota = module - elif isinstance(module, Battery): - self.__battery = module - elif isinstance(module, History): - self.__history = module - elif isinstance(module, GNSSBase): - self.__gnss = module - elif isinstance(module, CellLocator): - self.__cell = module - elif isinstance(module, WiFiLocator): - self.__wifi = module - elif isinstance(module, CoordinateSystemConvert): - self.__csc = module - elif isinstance(module, NetManager): - self.__net_manager = module - elif isinstance(module, Settings): - self.__settings = module - else: - return False - return True - - def running(self, args=None): - self.__business_start() - self.server_connect(None) - self.__business_queue.put((0, "ota_refresh")) - self.loc_report(None) - self.__business_queue.put((0, "check_ota")) - - def server_callback(self, args): - self.__business_queue.put((1, args)) - - def net_callback(self, args): - log.debug("net_callback args: %s" % str(args)) - if args[1] == 0: - self.__server.disconnect() - self.__server_reconn_timer.stop() - self.__server_reconn_timer.start(30 * 1000, 0, self.server_connect) - else: - self.__server_reconn_timer.stop() - self.server_connect(None) - - def loc_report(self, args): - self.__business_queue.put((0, "loc_report")) - - def server_connect(self, args): - if self.__server_conn_tag == 0: - self.__server_conn_tag = 1 - self.__business_queue.put((0, "server_connect")) diff --git a/code/tracker_tb.py b/code/tracker_tb.py deleted file mode 100644 index 522ec43..0000000 --- a/code/tracker_tb.py +++ /dev/null @@ -1,304 +0,0 @@ -# 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. - -""" -@file :tracker_tb.py -@author :Jack Sun (jack.sun@quectel.com) -@brief :Tracker by ThingsBoard. -@version :2.2.0 -@date :2023-04-14 14:30:13 -@copyright :Copyright (c) 2022 -""" - -import utime -import _thread -import osTimer -from misc import Power -from queue import Queue -from machine import RTC - -try: - from settings import Settings - from settings_user import UserConfig - from modules.battery import Battery - from modules.history import History - from modules.logging import getLogger - from modules.net_manage import NetManager - from modules.thingsboard import TBDeviceMQTTClient - from modules.power_manage import PowerManage, PMLock - from modules.location import GNSS, GNSSBase, CellLocator, WiFiLocator, CoordinateSystemConvert -except ImportError: - from usr.settings import Settings - from usr.settings_user import UserConfig - from usr.modules.battery import Battery - from usr.modules.history import History - from usr.modules.logging import getLogger - from usr.modules.net_manage import NetManager - from usr.modules.thingsboard import TBDeviceMQTTClient - from usr.modules.power_manage import PowerManage, PMLock - from usr.modules.location import GNSS, GNSSBase, CellLocator, WiFiLocator, CoordinateSystemConvert - -log = getLogger(__name__) - - -class Tracker: - - def __init__(self): - self.__server = None - self.__server_ota = None - self.__battery = None - self.__history = None - self.__gnss = None - self.__cell = None - self.__wifi = None - self.__csc = None - self.__net_manager = None - self.__settings = None - - self.__business_lock = PMLock("block") - self.__business_tid = None - self.__business_rtc = RTC() - self.__business_queue = Queue() - self.__business_tag = 0 - self.__server_ota_flag = 0 - self.__server_reconn_timer = osTimer() - self.__server_conn_tag = 0 - self.__server_reconn_count = 0 - self.__reset_tag = 0 - - def __business_start(self): - if not self.__business_tid or (self.__business_tid and not _thread.threadIsRunning(self.__business_tid)): - _thread.stack_size(0x2000) - self.__business_tid = _thread.start_new_thread(self.__business_running, ()) - - def __business_stop(self): - self.__business_tid = None - - def __business_running(self): - while self.__business_tid is not None or self.__business_queue.size() > 0: - data = self.__business_queue.get() - with self.__business_lock: - self.__business_tag = 1 - if data[0] == 0: - if data[1] == "loc_report": - self.__loc_report() - if data[1] == "server_connect": - self.__server_connect() - if data[0] == 1: - self.__server_option(data[1]) - self.__business_tag = 0 - - def __loc_report(self): - # Report current location. - loc_state, properties = self.__get_loc_data() - if loc_state == 1: - res = False - if self.__server.status: - res = self.__server.send_telemetry(properties) - if not res: - self.__history.write([properties]) - - # Report history location. - if self.__server.status: - self.__history_report() - - # Start report again timer. - user_cfg = self.__settings.read("user") - self.__set_rtc(user_cfg["work_cycle_period"], self.loc_report) - - def __history_report(self): - failed_datas = [] - his_datas = self.__history.read() - if his_datas["data"]: - for item in his_datas["data"]: - res = self.__server.send_telemetry(item) - if not res: - failed_datas.append(item) - if failed_datas: - self.__history.write(failed_datas) - - def __get_loc_data(self): - loc_state = 0 - loc_data = { - "Longitude": 0.0, - "Latitude": 0.0, - "Altitude": 0.0, - "Speed": 0.0, - } - loc_cfg = self.__settings.read("loc") - user_cfg = self.__settings.read("user") - if user_cfg["loc_method"] & UserConfig._loc_method.gps: - res = self.__gnss.read() - log.debug("gnss read %s" % str(res)) - if res["state"] == "A": - loc_data["Latitude"] = float(res["lat"]) * (1 if res["lat_dir"] == "N" else -1) - loc_data["Longitude"] = float(res["lng"]) * (1 if res["lng_dir"] == "E" else -1) - loc_data["Altitude"] = res["altitude"] - loc_data["Speed"] = res["speed"] - loc_state = 1 - if loc_state == 0 and user_cfg["loc_method"] & UserConfig._loc_method.cell: - res = self.__cell.read() - if isinstance(res, tuple): - loc_data["Longitude"] = res[0] - loc_data["Latitude"] = res[1] - loc_state = 1 - if loc_state == 0 and user_cfg["loc_method"] & UserConfig._loc_method.wifi: - res = self.__wifi.read() - if isinstance(res, tuple): - loc_data["Longitude"] = res[0] - loc_data["Latitude"] = res[1] - loc_state = 1 - if loc_state == 1 and loc_cfg["map_coordinate_system"] == "GCJ02": - lng, lat = self.__csc.wgs84_to_gcj02(loc_data["Longitude"], loc_data["Latitude"]) - loc_data["Longitude"] = lng - loc_data["Latitude"] = lat - return (loc_state, loc_data) - - def __set_rtc(self, period, callback): - self.__business_rtc.enable_alarm(0) - if callback and callable(callback): - self.__business_rtc.register_callback(callback) - atime = utime.localtime(utime.mktime(utime.localtime()) + period) - alarm_time = (atime[0], atime[1], atime[2], atime[6], atime[3], atime[4], atime[5], 0) - _res = self.__business_rtc.set_alarm(alarm_time) - log.debug("alarm_time: %s, set_alarm res %s." % (str(alarm_time), _res)) - return self.__business_rtc.enable_alarm(1) if _res == 0 else -1 - - def __server_connect(self): - if self.__net_manager.net_status(): - self.__server.disconnect() - self.__server.connect() - if not self.__server.status: - self.__server_reconn_timer.stop() - self.__server_reconn_timer.start(60 * 1000, 0, self.server_connect) - self.__server_reconn_count += 1 - else: - self.__server_reconn_count = 0 - - # When server not connect success after 20 miuntes, to reset device. - if self.__server_reconn_count >= 20: - _thread.stack_size(0x1000) - _thread.start_new_thread(self.__power_restart, ()) - self.__server_conn_tag = 0 - - def __server_option(self, args): - topic, data = args - log.debug("topic[%s]data[%s]" % args) - # TODO: Handle server data. - - def __power_restart(self): - if self.__reset_tag == 1: - return - self.__reset_tag = 1 - count = 0 - while (self.__business_queue.size() > 0 or self.__business_tag == 1) and count < 30: - count += 1 - utime.sleep(1) - log.debug("__power_restart") - Power.powerRestart() - - def add_module(self, module): - if isinstance(module, TBDeviceMQTTClient): - self.__server = module - elif isinstance(module, Battery): - self.__battery = module - elif isinstance(module, History): - self.__history = module - elif isinstance(module, GNSSBase): - self.__gnss = module - elif isinstance(module, CellLocator): - self.__cell = module - elif isinstance(module, WiFiLocator): - self.__wifi = module - elif isinstance(module, CoordinateSystemConvert): - self.__csc = module - elif isinstance(module, NetManager): - self.__net_manager = module - elif isinstance(module, Settings): - self.__settings = module - else: - return False - return True - - def running(self): - self.__business_start() - self.server_connect(None) - self.loc_report(None) - - def server_callback(self, topic, data): - self.__business_queue.put((1, (topic, data))) - - def net_callback(self, args): - log.debug("net_callback args: %s" % str(args)) - if args[1] == 0: - self.__server.disconnect() - self.__server_reconn_timer.stop() - self.__server_reconn_timer.start(30 * 1000, 0, self.server_connect) - else: - self.__server_reconn_timer.stop() - self.server_connect(None) - - def loc_report(self, args): - self.__business_queue.put((0, "loc_report")) - - def server_connect(self, args): - if self.__server_conn_tag == 0: - self.__server_conn_tag = 1 - self.__business_queue.put((0, "server_connect")) - - -if __name__ == "__main__": - # Init settings. - settings = Settings() - # Init battery. - battery = Battery() - # Init history - history = History() - # Init power manage and set device low energy. - power_manage = PowerManage() - power_manage.autosleep(1) - # Init net modules and start net connect. - net_manager = NetManager() - _thread.stack_size(0x1000) - _thread.start_new_thread(net_manager.net_connect, ()) - # Init GNSS modules and start reading and parsing gnss data. - loc_cfg = settings.read("loc") - gnss = GNSS(**loc_cfg["gps_cfg"]) - gnss.set_trans(0) - gnss.start() - # Init cell and wifi location modules. - cell = CellLocator(**loc_cfg["cell_cfg"]) - wifi = WiFiLocator(**loc_cfg["wifi_cfg"]) - # Init coordinate system convert modules. - cyc = CoordinateSystemConvert() - # Init server modules. - server_cfg = settings.read("server") - server = TBDeviceMQTTClient(**server_cfg) - # Init tracker business modules. - tracker = Tracker() - tracker.add_module(settings) - tracker.add_module(battery) - tracker.add_module(history) - tracker.add_module(net_manager) - tracker.add_module(server) - tracker.add_module(gnss) - tracker.add_module(cell) - tracker.add_module(wifi) - tracker.add_module(cyc) - # Set net modules callback. - net_manager.set_callback(tracker.net_callback) - # Set server modules callback. - server.set_callback(tracker.server_callback) - # Start tracker business. - tracker.running() diff --git a/docs/en/media/connect.png b/docs/en/media/connect.png deleted file mode 100644 index d41ac89..0000000 Binary files a/docs/en/media/connect.png and /dev/null differ diff --git a/docs/en/media/tracker_application.png b/docs/en/media/tracker_application.png deleted file mode 100644 index 8309e6d..0000000 Binary files a/docs/en/media/tracker_application.png and /dev/null differ diff --git a/docs/en/media/tracker_funcion.png b/docs/en/media/tracker_funcion.png deleted file mode 100644 index a7cbc61..0000000 Binary files a/docs/en/media/tracker_funcion.png and /dev/null differ diff --git a/docs/en/media/tracker_process.png b/docs/en/media/tracker_process.png deleted file mode 100644 index bf933ff..0000000 Binary files a/docs/en/media/tracker_process.png and /dev/null differ diff --git a/docs/zh/media/tracker_application.png b/docs/zh/media/tracker_application.png deleted file mode 100644 index 8309e6d..0000000 Binary files a/docs/zh/media/tracker_application.png and /dev/null differ diff --git a/docs/zh/media/tracker_funcion.png b/docs/zh/media/tracker_funcion.png deleted file mode 100644 index a7cbc61..0000000 Binary files a/docs/zh/media/tracker_funcion.png and /dev/null differ diff --git a/docs/zh/media/tracker_process.png b/docs/zh/media/tracker_process.png deleted file mode 100644 index bf933ff..0000000 Binary files a/docs/zh/media/tracker_process.png and /dev/null differ diff --git "a/docs/zh/media/\350\277\236\347\272\277.png" "b/docs/zh/media/\350\277\236\347\272\277.png" deleted file mode 100644 index d41ac89..0000000 Binary files "a/docs/zh/media/\350\277\236\347\272\277.png" and /dev/null differ diff --git a/object_model_demo/ali_cloud_object_model.json b/object_model_demo/ali_cloud_object_model.json deleted file mode 100644 index 2291165..0000000 --- a/object_model_demo/ali_cloud_object_model.json +++ /dev/null @@ -1,2291 +0,0 @@ -{ - "schema": "https://iotx-tsl.oss-ap-southeast-1.aliyuncs.com/schema.json", - "profile": { - "version": "1.0", - "productKey": "h3nqn03lil0" - }, - "properties": [ - { - "identifier": "GeoLocation", - "name": "地理位置", - "accessMode": "r", - "required": true, - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "Longitude", - "name": "经度", - "dataType": { - "type": "double", - "specs": { - "min": "-180", - "max": "180", - "unit": "°", - "unitName": "度", - "step": "0.01" - } - } - }, - { - "identifier": "Latitude", - "name": "纬度", - "dataType": { - "type": "double", - "specs": { - "min": "-90", - "max": "90", - "unit": "°", - "unitName": "度", - "step": "0.01" - } - } - }, - { - "identifier": "Altitude", - "name": "海拔", - "dataType": { - "type": "double", - "specs": { - "min": "0", - "max": "9999", - "unit": "m", - "unitName": "米", - "step": "0.01" - } - } - }, - { - "identifier": "CoordinateSystem", - "name": "坐标系统", - "dataType": { - "type": "enum", - "specs": { - "1": "WGS_84", - "2": "GCJ_02" - } - } - } - ] - } - }, - { - "identifier": "power_switch", - "name": "开关机", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "energy", - "name": "电量", - "accessMode": "r", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "100", - "unit": "%", - "unitName": "百分比", - "step": "1" - } - } - }, - { - "identifier": "phone_num", - "name": "电话号码", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "text", - "specs": { - "length": "11" - } - } - }, - { - "identifier": "loc_method", - "name": "定位方式", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "gps", - "name": "GPS", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "cell", - "name": "基站", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "wifi", - "name": "Wifi", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - } - ] - } - }, - { - "identifier": "work_mode", - "name": "工作模式", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "enum", - "specs": { - "1": "周期性模式", - "2": "智能模式" - } - } - }, - { - "identifier": "work_cycle_period", - "name": "工作模式循环周期", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "local_time", - "name": "本地时间", - "accessMode": "r", - "required": false, - "dataType": { - "type": "date", - "specs": {} - } - }, - { - "identifier": "low_power_alert_threshold", - "name": "低电报警阈值", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "low_power_shutdown_threshold", - "name": "低电关机阈值", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "sw_ota", - "name": "OTA功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_ota_auto_upgrade", - "name": "OTA自动升级功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_listen", - "name": "语音监听功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_record", - "name": "录音上报功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_fault_alert", - "name": "故障报警功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_low_power_alert", - "name": "低电报警功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_over_speed_alert", - "name": "超速报警功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_sim_abnormal_alert", - "name": "SIM卡异常报警功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_disassemble_alert", - "name": "拆卸报警功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_drive_behavior_alert", - "name": "驾驶行为报警功能", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "drive_behavior_code", - "name": "异常驾驶行为", - "accessMode": "r", - "required": false, - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "急起", - "2": "急停", - "3": "左急转弯", - "4": "右急转弯" - } - } - }, - { - "identifier": "power_restart", - "name": "模块重启", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "enum", - "specs": { - "1": "重启" - } - } - }, - { - "identifier": "over_speed_threshold", - "name": "超速报警阈值", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "132", - "unit": "km/h", - "unitName": "千米每小时", - "step": "1" - } - } - }, - { - "identifier": "device_module_status", - "name": "设备模块状态", - "accessMode": "r", - "required": false, - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "net", - "name": "网络状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "location", - "name": "定位状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "temp_sensor", - "name": "温湿度传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "light_sensor", - "name": "光照传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "move_sensor", - "name": "三轴加速传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "mike", - "name": "麦克风状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - } - ] - } - }, - { - "identifier": "gps_mode", - "name": "GPS模块类型", - "accessMode": "r", - "required": false, - "dataType": { - "type": "enum", - "specs": { - "0": "无GPS模块", - "1": "内置GPS模块", - "2": "外置GPS模块" - } - } - }, - { - "identifier": "user_ota_action", - "name": "确认OTA升级", - "accessMode": "rw", - "desc": "用户操作,只写功能", - "required": false, - "dataType": { - "type": "enum", - "specs": { - "0": "取消升级", - "1": "确认升级" - } - } - }, - { - "identifier": "ota_status", - "name": "OTA升级状态", - "accessMode": "r", - "required": false, - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "sys_current_version", - "name": "系统当前版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "sys_target_version", - "name": "系统目标版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "app_current_version", - "name": "应用当前版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "app_target_version", - "name": "应用目标版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "upgrade_module", - "name": "当前升级模块", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "系统", - "2": "应用" - } - } - }, - { - "identifier": "upgrade_status", - "name": "当前升级状态", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "待升级", - "2": "升级中", - "3": "升级成功", - "4": "升级失败" - } - } - } - ] - } - }, - { - "identifier": "voltage", - "name": "电池电压", - "accessMode": "r", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "2147483647", - "unit": "mV", - "unitName": "毫伏", - "step": "1" - } - } - }, - { - "identifier": "current_speed", - "name": "当前时速", - "accessMode": "r", - "required": false, - "dataType": { - "type": "float", - "specs": { - "min": "0", - "max": "200.00", - "unit": "km/h", - "unitName": "千米每小时", - "step": "0.01" - } - } - }, - { - "identifier": "work_mode_timeline", - "name": "休眠策略参考时间", - "accessMode": "rw", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "3600", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "loc_gps_read_timeout", - "name": "GPS信息读取超时时间", - "accessMode": "rw", - "desc": "-1:一直等待直到读取到数据才进行返回\n0:不等待,读取接口返回数据后立即返回\n>0: 等待超时时间,单位秒", - "required": false, - "dataType": { - "type": "int", - "specs": { - "min": "-1", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "temperature", - "name": "设备温度", - "accessMode": "r", - "required": false, - "dataType": { - "type": "double", - "specs": { - "min": "-40", - "max": "125", - "unit": "°C", - "unitName": "摄氏度", - "step": "0.01" - } - } - }, - { - "identifier": "humidity", - "name": "设备湿度", - "accessMode": "r", - "required": false, - "dataType": { - "type": "double", - "specs": { - "min": "0", - "max": "100", - "unit": "%RH", - "unitName": "相对湿度", - "step": "0.01" - } - } - } - ], - "events": [ - { - "identifier": "post", - "name": "post", - "type": "info", - "required": true, - "desc": "属性上报", - "method": "thing.event.property.post", - "outputData": [ - { - "identifier": "GeoLocation", - "name": "地理位置", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "Longitude", - "name": "经度", - "dataType": { - "type": "double", - "specs": { - "min": "-180", - "max": "180", - "unit": "°", - "unitName": "度", - "step": "0.01" - } - } - }, - { - "identifier": "Latitude", - "name": "纬度", - "dataType": { - "type": "double", - "specs": { - "min": "-90", - "max": "90", - "unit": "°", - "unitName": "度", - "step": "0.01" - } - } - }, - { - "identifier": "Altitude", - "name": "海拔", - "dataType": { - "type": "double", - "specs": { - "min": "0", - "max": "9999", - "unit": "m", - "unitName": "米", - "step": "0.01" - } - } - }, - { - "identifier": "CoordinateSystem", - "name": "坐标系统", - "dataType": { - "type": "enum", - "specs": { - "1": "WGS_84", - "2": "GCJ_02" - } - } - } - ] - } - }, - { - "identifier": "power_switch", - "name": "开关机", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "energy", - "name": "电量", - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "100", - "unit": "%", - "unitName": "百分比", - "step": "1" - } - } - }, - { - "identifier": "phone_num", - "name": "电话号码", - "dataType": { - "type": "text", - "specs": { - "length": "11" - } - } - }, - { - "identifier": "loc_method", - "name": "定位方式", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "gps", - "name": "GPS", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "cell", - "name": "基站", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "wifi", - "name": "Wifi", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - } - ] - } - }, - { - "identifier": "work_mode", - "name": "工作模式", - "dataType": { - "type": "enum", - "specs": { - "1": "周期性模式", - "2": "智能模式" - } - } - }, - { - "identifier": "work_cycle_period", - "name": "工作模式循环周期", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "local_time", - "name": "本地时间", - "dataType": { - "type": "date", - "specs": {} - } - }, - { - "identifier": "low_power_alert_threshold", - "name": "低电报警阈值", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "low_power_shutdown_threshold", - "name": "低电关机阈值", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "sw_ota", - "name": "OTA功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_ota_auto_upgrade", - "name": "OTA自动升级功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_listen", - "name": "语音监听功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_record", - "name": "录音上报功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_fault_alert", - "name": "故障报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_low_power_alert", - "name": "低电报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_over_speed_alert", - "name": "超速报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_sim_abnormal_alert", - "name": "SIM卡异常报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_disassemble_alert", - "name": "拆卸报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_drive_behavior_alert", - "name": "驾驶行为报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "drive_behavior_code", - "name": "异常驾驶行为", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "急起", - "2": "急停", - "3": "左急转弯", - "4": "右急转弯" - } - } - }, - { - "identifier": "power_restart", - "name": "模块重启", - "dataType": { - "type": "enum", - "specs": { - "1": "重启" - } - } - }, - { - "identifier": "over_speed_threshold", - "name": "超速报警阈值", - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "132", - "unit": "km/h", - "unitName": "千米每小时", - "step": "1" - } - } - }, - { - "identifier": "device_module_status", - "name": "设备模块状态", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "net", - "name": "网络状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "location", - "name": "定位状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "temp_sensor", - "name": "温湿度传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "light_sensor", - "name": "光照传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "move_sensor", - "name": "三轴加速传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "mike", - "name": "麦克风状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - } - ] - } - }, - { - "identifier": "gps_mode", - "name": "GPS模块类型", - "dataType": { - "type": "enum", - "specs": { - "0": "无GPS模块", - "1": "内置GPS模块", - "2": "外置GPS模块" - } - } - }, - { - "identifier": "user_ota_action", - "name": "确认OTA升级", - "dataType": { - "type": "enum", - "specs": { - "0": "取消升级", - "1": "确认升级" - } - } - }, - { - "identifier": "ota_status", - "name": "OTA升级状态", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "sys_current_version", - "name": "系统当前版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "sys_target_version", - "name": "系统目标版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "app_current_version", - "name": "应用当前版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "app_target_version", - "name": "应用目标版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "upgrade_module", - "name": "当前升级模块", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "系统", - "2": "应用" - } - } - }, - { - "identifier": "upgrade_status", - "name": "当前升级状态", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "待升级", - "2": "升级中", - "3": "升级成功", - "4": "升级失败" - } - } - } - ] - } - }, - { - "identifier": "voltage", - "name": "电池电压", - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "2147483647", - "unit": "mV", - "unitName": "毫伏", - "step": "1" - } - } - }, - { - "identifier": "current_speed", - "name": "当前时速", - "dataType": { - "type": "float", - "specs": { - "min": "0", - "max": "200.00", - "unit": "km/h", - "unitName": "千米每小时", - "step": "0.01" - } - } - }, - { - "identifier": "work_mode_timeline", - "name": "休眠策略参考时间", - "dataType": { - "type": "int", - "specs": { - "min": "3600", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "loc_gps_read_timeout", - "name": "GPS信息读取超时时间", - "dataType": { - "type": "int", - "specs": { - "min": "-1", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "temperature", - "name": "设备温度", - "dataType": { - "type": "double", - "specs": { - "min": "-40", - "max": "125", - "unit": "°C", - "unitName": "摄氏度", - "step": "0.01" - } - } - }, - { - "identifier": "humidity", - "name": "设备湿度", - "dataType": { - "type": "double", - "specs": { - "min": "0", - "max": "100", - "unit": "%RH", - "unitName": "相对湿度", - "step": "0.01" - } - } - } - ] - }, - { - "identifier": "sos_alert", - "name": "SOS报警", - "type": "alert", - "required": false, - "method": "thing.event.sos_alert.post", - "outputData": [] - }, - { - "identifier": "fault_alert", - "name": "故障报警", - "type": "error", - "required": false, - "method": "thing.event.fault_alert.post", - "outputData": [] - }, - { - "identifier": "low_power_alert", - "name": "低电报警", - "type": "alert", - "required": false, - "method": "thing.event.low_power_alert.post", - "outputData": [] - }, - { - "identifier": "sim_abnormal_alert", - "name": "SIM卡异常报警", - "type": "alert", - "required": false, - "method": "thing.event.sim_abnormal_alert.post", - "outputData": [] - }, - { - "identifier": "drive_behavior_alert", - "name": "异常驾驶行为报警", - "type": "alert", - "required": false, - "method": "thing.event.drive_behavior_alert.post", - "outputData": [] - }, - { - "identifier": "disassemble_alert", - "name": "拆卸报警", - "type": "alert", - "required": false, - "method": "thing.event.disassemble_alert.post", - "outputData": [] - }, - { - "identifier": "over_speed_alert", - "name": "超速报警", - "type": "alert", - "required": false, - "method": "thing.event.over_speed_alert.post", - "outputData": [] - } - ], - "services": [ - { - "identifier": "set", - "name": "set", - "required": true, - "callType": "async", - "desc": "属性设置", - "method": "thing.service.property.set", - "inputData": [ - { - "identifier": "power_switch", - "name": "开关机", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "phone_num", - "name": "电话号码", - "dataType": { - "type": "text", - "specs": { - "length": "11" - } - } - }, - { - "identifier": "loc_method", - "name": "定位方式", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "gps", - "name": "GPS", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "cell", - "name": "基站", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "wifi", - "name": "Wifi", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - } - ] - } - }, - { - "identifier": "work_mode", - "name": "工作模式", - "dataType": { - "type": "enum", - "specs": { - "1": "周期性模式", - "2": "智能模式" - } - } - }, - { - "identifier": "work_cycle_period", - "name": "工作模式循环周期", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "low_power_alert_threshold", - "name": "低电报警阈值", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "low_power_shutdown_threshold", - "name": "低电关机阈值", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "sw_ota", - "name": "OTA功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_ota_auto_upgrade", - "name": "OTA自动升级功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_listen", - "name": "语音监听功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_record", - "name": "录音上报功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_fault_alert", - "name": "故障报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_low_power_alert", - "name": "低电报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_over_speed_alert", - "name": "超速报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_sim_abnormal_alert", - "name": "SIM卡异常报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_disassemble_alert", - "name": "拆卸报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_drive_behavior_alert", - "name": "驾驶行为报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "power_restart", - "name": "模块重启", - "dataType": { - "type": "enum", - "specs": { - "1": "重启" - } - } - }, - { - "identifier": "over_speed_threshold", - "name": "超速报警阈值", - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "132", - "unit": "km/h", - "unitName": "千米每小时", - "step": "1" - } - } - }, - { - "identifier": "user_ota_action", - "name": "确认OTA升级", - "dataType": { - "type": "enum", - "specs": { - "0": "取消升级", - "1": "确认升级" - } - } - }, - { - "identifier": "work_mode_timeline", - "name": "休眠策略参考时间", - "dataType": { - "type": "int", - "specs": { - "min": "3600", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "loc_gps_read_timeout", - "name": "GPS信息读取超时时间", - "dataType": { - "type": "int", - "specs": { - "min": "-1", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - } - ], - "outputData": [] - }, - { - "identifier": "get", - "name": "get", - "required": true, - "callType": "async", - "desc": "属性获取", - "method": "thing.service.property.get", - "inputData": [ - "GeoLocation", - "power_switch", - "energy", - "phone_num", - "loc_method", - "work_mode", - "work_cycle_period", - "local_time", - "low_power_alert_threshold", - "low_power_shutdown_threshold", - "sw_ota", - "sw_ota_auto_upgrade", - "sw_voice_listen", - "sw_voice_record", - "sw_fault_alert", - "sw_low_power_alert", - "sw_over_speed_alert", - "sw_sim_abnormal_alert", - "sw_disassemble_alert", - "sw_drive_behavior_alert", - "drive_behavior_code", - "power_restart", - "over_speed_threshold", - "device_module_status", - "gps_mode", - "user_ota_action", - "ota_status", - "voltage", - "current_speed", - "work_mode_timeline", - "loc_gps_read_timeout", - "temperature", - "humidity" - ], - "outputData": [ - { - "identifier": "GeoLocation", - "name": "地理位置", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "Longitude", - "name": "经度", - "dataType": { - "type": "double", - "specs": { - "min": "-180", - "max": "180", - "unit": "°", - "unitName": "度", - "step": "0.01" - } - } - }, - { - "identifier": "Latitude", - "name": "纬度", - "dataType": { - "type": "double", - "specs": { - "min": "-90", - "max": "90", - "unit": "°", - "unitName": "度", - "step": "0.01" - } - } - }, - { - "identifier": "Altitude", - "name": "海拔", - "dataType": { - "type": "double", - "specs": { - "min": "0", - "max": "9999", - "unit": "m", - "unitName": "米", - "step": "0.01" - } - } - }, - { - "identifier": "CoordinateSystem", - "name": "坐标系统", - "dataType": { - "type": "enum", - "specs": { - "1": "WGS_84", - "2": "GCJ_02" - } - } - } - ] - } - }, - { - "identifier": "power_switch", - "name": "开关机", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "energy", - "name": "电量", - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "100", - "unit": "%", - "unitName": "百分比", - "step": "1" - } - } - }, - { - "identifier": "phone_num", - "name": "电话号码", - "dataType": { - "type": "text", - "specs": { - "length": "11" - } - } - }, - { - "identifier": "loc_method", - "name": "定位方式", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "gps", - "name": "GPS", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "cell", - "name": "基站", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - }, - { - "identifier": "wifi", - "name": "Wifi", - "dataType": { - "type": "bool", - "specs": { - "0": "禁用", - "1": "启用" - } - } - } - ] - } - }, - { - "identifier": "work_mode", - "name": "工作模式", - "dataType": { - "type": "enum", - "specs": { - "1": "周期性模式", - "2": "智能模式" - } - } - }, - { - "identifier": "work_cycle_period", - "name": "工作模式循环周期", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "local_time", - "name": "本地时间", - "dataType": { - "type": "date", - "specs": {} - } - }, - { - "identifier": "low_power_alert_threshold", - "name": "低电报警阈值", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "low_power_shutdown_threshold", - "name": "低电关机阈值", - "dataType": { - "type": "int", - "specs": { - "min": "5", - "max": "30", - "step": "1" - } - } - }, - { - "identifier": "sw_ota", - "name": "OTA功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_ota_auto_upgrade", - "name": "OTA自动升级功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_listen", - "name": "语音监听功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_voice_record", - "name": "录音上报功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_fault_alert", - "name": "故障报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_low_power_alert", - "name": "低电报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_over_speed_alert", - "name": "超速报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_sim_abnormal_alert", - "name": "SIM卡异常报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_disassemble_alert", - "name": "拆卸报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "sw_drive_behavior_alert", - "name": "驾驶行为报警功能", - "dataType": { - "type": "bool", - "specs": { - "0": "关", - "1": "开" - } - } - }, - { - "identifier": "drive_behavior_code", - "name": "异常驾驶行为", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "急起", - "2": "急停", - "3": "左急转弯", - "4": "右急转弯" - } - } - }, - { - "identifier": "power_restart", - "name": "模块重启", - "dataType": { - "type": "enum", - "specs": { - "1": "重启" - } - } - }, - { - "identifier": "over_speed_threshold", - "name": "超速报警阈值", - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "132", - "unit": "km/h", - "unitName": "千米每小时", - "step": "1" - } - } - }, - { - "identifier": "device_module_status", - "name": "设备模块状态", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "net", - "name": "网络状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "location", - "name": "定位状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "temp_sensor", - "name": "温湿度传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "light_sensor", - "name": "光照传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "move_sensor", - "name": "三轴加速传感器状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - }, - { - "identifier": "mike", - "name": "麦克风状态", - "dataType": { - "type": "enum", - "specs": { - "0": "异常", - "1": "正常" - } - } - } - ] - } - }, - { - "identifier": "gps_mode", - "name": "GPS模块类型", - "dataType": { - "type": "enum", - "specs": { - "0": "无GPS模块", - "1": "内置GPS模块", - "2": "外置GPS模块" - } - } - }, - { - "identifier": "user_ota_action", - "name": "确认OTA升级", - "dataType": { - "type": "enum", - "specs": { - "0": "取消升级", - "1": "确认升级" - } - } - }, - { - "identifier": "ota_status", - "name": "OTA升级状态", - "dataType": { - "type": "struct", - "specs": [ - { - "identifier": "sys_current_version", - "name": "系统当前版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "sys_target_version", - "name": "系统目标版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "app_current_version", - "name": "应用当前版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "app_target_version", - "name": "应用目标版本", - "dataType": { - "type": "text", - "specs": { - "length": "1024" - } - } - }, - { - "identifier": "upgrade_module", - "name": "当前升级模块", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "系统", - "2": "应用" - } - } - }, - { - "identifier": "upgrade_status", - "name": "当前升级状态", - "dataType": { - "type": "enum", - "specs": { - "0": "无", - "1": "待升级", - "2": "升级中", - "3": "升级成功", - "4": "升级失败" - } - } - } - ] - } - }, - { - "identifier": "voltage", - "name": "电池电压", - "dataType": { - "type": "int", - "specs": { - "min": "0", - "max": "2147483647", - "unit": "mV", - "unitName": "毫伏", - "step": "1" - } - } - }, - { - "identifier": "current_speed", - "name": "当前时速", - "dataType": { - "type": "float", - "specs": { - "min": "0", - "max": "200.00", - "unit": "km/h", - "unitName": "千米每小时", - "step": "0.01" - } - } - }, - { - "identifier": "work_mode_timeline", - "name": "休眠策略参考时间", - "dataType": { - "type": "int", - "specs": { - "min": "3600", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "loc_gps_read_timeout", - "name": "GPS信息读取超时时间", - "dataType": { - "type": "int", - "specs": { - "min": "-1", - "max": "2147483647", - "unit": "s", - "unitName": "秒", - "step": "1" - } - } - }, - { - "identifier": "temperature", - "name": "设备温度", - "dataType": { - "type": "double", - "specs": { - "min": "-40", - "max": "125", - "unit": "°C", - "unitName": "摄氏度", - "step": "0.01" - } - } - }, - { - "identifier": "humidity", - "name": "设备湿度", - "dataType": { - "type": "double", - "specs": { - "min": "0", - "max": "100", - "unit": "%RH", - "unitName": "相对湿度", - "step": "0.01" - } - } - } - ] - } - ] -} \ No newline at end of file