├── .gitignore ├── __init__.py ├── code ├── __init__.py ├── modules │ ├── __init__.py │ ├── net_manager.py │ ├── logging.py │ ├── serial.py │ ├── history.py │ ├── remote.py │ ├── txyunIot.py │ ├── mqttIot.py │ ├── common.py │ ├── huawei_cloud.py │ └── socketIot.py ├── settings_user.py ├── dtu_config.json ├── settings.py ├── dtu.py └── dtu_transaction.py ├── .gitmodules ├── docs ├── en │ ├── media │ │ ├── DP-DTU-Q600.png │ │ ├── quec_ota_model.png │ │ ├── DTU_User_Guides │ │ │ ├── DTU.png │ │ │ ├── port.png │ │ │ ├── CP2102.jpg │ │ │ ├── antenna.jpg │ │ │ ├── pc_uart.png │ │ │ ├── aliyun_dn.png │ │ │ ├── dev_board.png │ │ │ ├── dtu_recive.jpg │ │ │ ├── qpycom_dl.png │ │ │ ├── qpycom_run.jpg │ │ │ ├── aliyun_pkps.png │ │ │ ├── aliyun_send.jpg │ │ │ ├── cloud_recive.jpg │ │ │ ├── pc_get_recive.jpg │ │ │ ├── pc_uart_send.png │ │ │ ├── qpycom_proj.png │ │ │ ├── qpycom_run2.png │ │ │ ├── qpycom_run3.png │ │ │ ├── board_line_link.png │ │ │ ├── qpycom_add_file.jpg │ │ │ ├── qpycom_download.jpg │ │ │ ├── qpycom_new_proj.png │ │ │ ├── config_serial_id.png │ │ │ ├── qpycom_run_output.png │ │ │ ├── qpycom_run_success.jpg │ │ │ ├── qpycom_select_port.png │ │ │ └── qpycon_draft_file.png │ │ ├── dtu_frame_diagram.jpg │ │ ├── quec_ota_fota_plain.png │ │ ├── quec_ota_sota_plain.png │ │ ├── aliyun_ota_fota_plain.png │ │ ├── aliyun_ota_sota_plain.png │ │ ├── dtu_ota_flow_diagram.jpg │ │ ├── aliyun_ota_fota_module.png │ │ ├── aliyun_ota_sota_module.png │ │ ├── DTU_GUI_User_Guides │ │ │ ├── dtugui.jpg │ │ │ ├── gui_apn.png │ │ │ ├── gui_edit.png │ │ │ ├── gui_gpio.png │ │ │ ├── gui_input.png │ │ │ ├── gui_pwd.png │ │ │ ├── gui_pwd2.png │ │ │ ├── gui_opt_hex.png │ │ │ ├── gui_secret.png │ │ │ ├── gui_serial.png │ │ │ ├── gui_socket.jpg │ │ │ ├── gui_channel_at.png │ │ │ ├── gui_get_imei.jpg │ │ │ ├── gui_open_port.png │ │ │ ├── gui_opt_format.png │ │ │ ├── gui_opt_time.png │ │ │ ├── gui_quecthing.png │ │ │ ├── gui_send_cmd.png │ │ │ ├── gui_send_frame.jpg │ │ │ ├── gui_tx_secret.png │ │ │ ├── gui_channel_http.png │ │ │ ├── gui_channel_mqtt.png │ │ │ ├── gui_check_config.png │ │ │ ├── gui_config_page1.jpg │ │ │ ├── gui_mqtt_config.jpg │ │ │ ├── gui_send_cmd_pw.png │ │ │ ├── gui_aliyun_config.jpg │ │ │ ├── gui_channel_socket.png │ │ │ ├── gui_get_dtu_config.jpg │ │ │ ├── gui_import_config.png │ │ │ ├── gui_open_port_done.jpg │ │ │ ├── gui_open_port_done.png │ │ │ ├── gui_quecthing_config.jpg │ │ │ └── gui_get_dtu_config_uart_data.jpg │ │ ├── quec_ota_fota_upgrade_process.png │ │ ├── quec_ota_fota_version_package.png │ │ ├── quec_ota_model_add_mcu_module.png │ │ ├── quec_ota_sota_upgrade_process.png │ │ ├── quec_ota_sota_version_package.png │ │ ├── aliyun_ota_fota_upgrade_package.png │ │ ├── aliyun_ota_fota_upgrade_process.png │ │ ├── aliyun_ota_sota_upgrade_package.png │ │ └── aliyun_ota_sota_upgrade_process.png │ ├── DTU_OTA_Upgrade_User_Guide.md │ ├── DTU_GUI_Tool_User_Guide.md │ └── DTU_Solution_Development_Guide.md └── zh │ ├── media │ ├── DP-DTU-Q600.png │ ├── quec_ota_model.png │ ├── DTU_User_Guides │ │ ├── DTU.png │ │ ├── port.png │ │ ├── CP2102.jpg │ │ ├── antenna.jpg │ │ ├── pc_uart.png │ │ ├── aliyun_dn.png │ │ ├── dev_board.png │ │ ├── dtu_recive.jpg │ │ ├── qpycom_dl.png │ │ ├── qpycom_run.jpg │ │ ├── aliyun_pkps.png │ │ ├── aliyun_send.jpg │ │ ├── cloud_recive.jpg │ │ ├── pc_get_recive.jpg │ │ ├── pc_uart_send.png │ │ ├── qpycom_proj.png │ │ ├── qpycom_run2.png │ │ ├── qpycom_run3.png │ │ ├── board_line_link.png │ │ ├── qpycom_add_file.jpg │ │ ├── qpycom_download.jpg │ │ ├── qpycom_new_proj.png │ │ ├── config_serial_id.png │ │ ├── qpycom_run_output.png │ │ ├── qpycom_run_success.jpg │ │ ├── qpycom_select_port.png │ │ └── qpycon_draft_file.png │ ├── dtu_frame_diagram.jpg │ ├── quec_ota_fota_plain.png │ ├── quec_ota_sota_plain.png │ ├── aliyun_ota_fota_plain.png │ ├── aliyun_ota_sota_plain.png │ ├── dtu_ota_flow_diagram.jpg │ ├── aliyun_ota_fota_module.png │ ├── aliyun_ota_sota_module.png │ ├── DTU_GUI_User_Guides │ │ ├── dtugui.jpg │ │ ├── gui_apn.png │ │ ├── gui_edit.png │ │ ├── gui_gpio.png │ │ ├── gui_input.png │ │ ├── gui_pwd.png │ │ ├── gui_pwd2.png │ │ ├── gui_opt_hex.png │ │ ├── gui_secret.png │ │ ├── gui_serial.png │ │ ├── gui_socket.jpg │ │ ├── gui_channel_at.png │ │ ├── gui_get_imei.jpg │ │ ├── gui_open_port.png │ │ ├── gui_opt_format.png │ │ ├── gui_opt_time.png │ │ ├── gui_quecthing.png │ │ ├── gui_send_cmd.png │ │ ├── gui_send_frame.jpg │ │ ├── gui_tx_secret.png │ │ ├── gui_channel_http.png │ │ ├── gui_channel_mqtt.png │ │ ├── gui_check_config.png │ │ ├── gui_config_page1.jpg │ │ ├── gui_mqtt_config.jpg │ │ ├── gui_send_cmd_pw.png │ │ ├── gui_aliyun_config.jpg │ │ ├── gui_channel_socket.png │ │ ├── gui_get_dtu_config.jpg │ │ ├── gui_import_config.png │ │ ├── gui_open_port_done.jpg │ │ ├── gui_open_port_done.png │ │ ├── gui_quecthing_config.jpg │ │ └── gui_get_dtu_config_uart_data.jpg │ ├── quec_ota_fota_upgrade_process.png │ ├── quec_ota_fota_version_package.png │ ├── quec_ota_model_add_mcu_module.png │ ├── quec_ota_sota_upgrade_process.png │ ├── quec_ota_sota_version_package.png │ ├── aliyun_ota_fota_upgrade_package.png │ ├── aliyun_ota_fota_upgrade_process.png │ ├── aliyun_ota_sota_upgrade_package.png │ └── aliyun_ota_sota_upgrade_process.png │ ├── DTU GUI 工具使用说明.md │ ├── DTU OTA 升级用户指导手册.md │ └── DTU 公版方案用户开发手册.md ├── LICENSE ├── CHANGELOG.md ├── README.zh.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | usr -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dtu_tool"] 2 | path = dtu_tool 3 | url = https://github.com/QuecPython/DTU-tool.git 4 | -------------------------------------------------------------------------------- /docs/en/media/DP-DTU-Q600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DP-DTU-Q600.png -------------------------------------------------------------------------------- /docs/zh/media/DP-DTU-Q600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DP-DTU-Q600.png -------------------------------------------------------------------------------- /docs/en/media/quec_ota_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_model.png -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_model.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/DTU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/DTU.png -------------------------------------------------------------------------------- /docs/en/media/dtu_frame_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/dtu_frame_diagram.jpg -------------------------------------------------------------------------------- /docs/en/media/quec_ota_fota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_fota_plain.png -------------------------------------------------------------------------------- /docs/en/media/quec_ota_sota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_sota_plain.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/DTU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/DTU.png -------------------------------------------------------------------------------- /docs/zh/media/dtu_frame_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/dtu_frame_diagram.jpg -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_fota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_fota_plain.png -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_sota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_sota_plain.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/port.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_fota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_fota_plain.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_sota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_sota_plain.png -------------------------------------------------------------------------------- /docs/en/media/dtu_ota_flow_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/dtu_ota_flow_diagram.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/port.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_fota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_fota_plain.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_sota_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_sota_plain.png -------------------------------------------------------------------------------- /docs/zh/media/dtu_ota_flow_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/dtu_ota_flow_diagram.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/CP2102.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/CP2102.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/antenna.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/antenna.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/pc_uart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/pc_uart.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_fota_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_fota_module.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_sota_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_sota_module.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/CP2102.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/CP2102.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/antenna.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/antenna.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/pc_uart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/pc_uart.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_fota_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_fota_module.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_sota_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_sota_module.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/dtugui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/dtugui.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/aliyun_dn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/aliyun_dn.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/dev_board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/dev_board.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/dtu_recive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/dtu_recive.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_dl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_dl.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_run.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_run.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/dtugui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/dtugui.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/aliyun_dn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/aliyun_dn.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/dev_board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/dev_board.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/dtu_recive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/dtu_recive.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_dl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_dl.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_run.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_run.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_apn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_apn.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_edit.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_gpio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_gpio.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_input.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_pwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_pwd.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_pwd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_pwd2.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/aliyun_pkps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/aliyun_pkps.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/aliyun_send.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/aliyun_send.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/cloud_recive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/cloud_recive.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/pc_get_recive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/pc_get_recive.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/pc_uart_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/pc_uart_send.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_proj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_proj.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_run2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_run2.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_run3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_run3.png -------------------------------------------------------------------------------- /docs/en/media/quec_ota_fota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_fota_upgrade_process.png -------------------------------------------------------------------------------- /docs/en/media/quec_ota_fota_version_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_fota_version_package.png -------------------------------------------------------------------------------- /docs/en/media/quec_ota_model_add_mcu_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_model_add_mcu_module.png -------------------------------------------------------------------------------- /docs/en/media/quec_ota_sota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_sota_upgrade_process.png -------------------------------------------------------------------------------- /docs/en/media/quec_ota_sota_version_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/quec_ota_sota_version_package.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_apn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_apn.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_edit.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_gpio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_gpio.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_input.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_pwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_pwd.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_pwd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_pwd2.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/aliyun_pkps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/aliyun_pkps.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/aliyun_send.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/aliyun_send.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/cloud_recive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/cloud_recive.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/pc_get_recive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/pc_get_recive.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/pc_uart_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/pc_uart_send.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_proj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_proj.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_run2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_run2.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_run3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_run3.png -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_fota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_fota_upgrade_process.png -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_fota_version_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_fota_version_package.png -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_model_add_mcu_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_model_add_mcu_module.png -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_sota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_sota_upgrade_process.png -------------------------------------------------------------------------------- /docs/zh/media/quec_ota_sota_version_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/quec_ota_sota_version_package.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_opt_hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_opt_hex.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_secret.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_serial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_serial.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_socket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_socket.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/board_line_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/board_line_link.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_add_file.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_add_file.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_download.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_download.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_new_proj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_new_proj.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_fota_upgrade_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_fota_upgrade_package.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_fota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_fota_upgrade_process.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_sota_upgrade_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_sota_upgrade_package.png -------------------------------------------------------------------------------- /docs/en/media/aliyun_ota_sota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/aliyun_ota_sota_upgrade_process.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_opt_hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_opt_hex.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_secret.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_serial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_serial.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_socket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_socket.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/board_line_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/board_line_link.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_add_file.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_add_file.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_download.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_download.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_new_proj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_new_proj.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_fota_upgrade_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_fota_upgrade_package.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_fota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_fota_upgrade_process.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_sota_upgrade_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_sota_upgrade_package.png -------------------------------------------------------------------------------- /docs/zh/media/aliyun_ota_sota_upgrade_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/aliyun_ota_sota_upgrade_process.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_channel_at.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_channel_at.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_get_imei.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_get_imei.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_open_port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_open_port.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_opt_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_opt_format.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_opt_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_opt_time.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_quecthing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_quecthing.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_send_cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_send_cmd.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_send_frame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_send_frame.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_tx_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_tx_secret.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/config_serial_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/config_serial_id.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_run_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_run_output.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_run_success.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_run_success.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycom_select_port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycom_select_port.png -------------------------------------------------------------------------------- /docs/en/media/DTU_User_Guides/qpycon_draft_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_User_Guides/qpycon_draft_file.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_channel_at.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_channel_at.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_get_imei.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_get_imei.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_open_port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_open_port.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_opt_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_opt_format.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_opt_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_opt_time.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_quecthing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_quecthing.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_send_cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_send_cmd.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_send_frame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_send_frame.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_tx_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_tx_secret.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/config_serial_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/config_serial_id.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_run_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_run_output.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_run_success.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_run_success.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycom_select_port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycom_select_port.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_User_Guides/qpycon_draft_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_User_Guides/qpycon_draft_file.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_channel_http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_channel_http.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_channel_mqtt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_channel_mqtt.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_check_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_check_config.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_config_page1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_config_page1.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_mqtt_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_mqtt_config.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_send_cmd_pw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_send_cmd_pw.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_channel_http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_channel_http.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_channel_mqtt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_channel_mqtt.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_check_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_check_config.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_config_page1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_config_page1.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_mqtt_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_mqtt_config.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_send_cmd_pw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_send_cmd_pw.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_aliyun_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_aliyun_config.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_channel_socket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_channel_socket.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_get_dtu_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_get_dtu_config.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_import_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_import_config.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_open_port_done.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_open_port_done.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_open_port_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_open_port_done.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_aliyun_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_aliyun_config.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_channel_socket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_channel_socket.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_get_dtu_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_get_dtu_config.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_import_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_import_config.png -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_open_port_done.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_open_port_done.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_open_port_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_open_port_done.png -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_quecthing_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_quecthing_config.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_quecthing_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_quecthing_config.jpg -------------------------------------------------------------------------------- /docs/en/media/DTU_GUI_User_Guides/gui_get_dtu_config_uart_data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/en/media/DTU_GUI_User_Guides/gui_get_dtu_config_uart_data.jpg -------------------------------------------------------------------------------- /docs/zh/media/DTU_GUI_User_Guides/gui_get_dtu_config_uart_data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuecPython/solution-DTU/HEAD/docs/zh/media/DTU_GUI_User_Guides/gui_get_dtu_config_uart_data.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | All significant changes to this project will be documented in this file. 4 | 5 | ## [v2.0.0] - 2022-05-30 6 | 7 | - Adjusted the overall project structure and refactored each module using the observer pattern design. 8 | - Unified all cloud interfaces and middleware Remote interfaces. 9 | 10 | ## [v2.0.1] - 2022-07-05 11 | 12 | - Fixed a bug where only one .py file could be upgraded when updating the Quectel Cloud project script files. 13 | - Fixed a bug where memory allocation failed in gc when extracting tar packages during Quectel Cloud project script upgrades. 14 | 15 | ## [v3.0.0] - 2022-08-17 16 | 17 | - Removed the command mode and Mosbus mode of DTU, only retaining the transparent transmission mode. 18 | - Removed the code and functionality related to the DTU GUI channel. 19 | - DTU now only supports MQTT and TCP protocols. 20 | 21 | ## [v3.1.0] - 2025-06-05 22 | 23 | - Removed Quec Thing support 24 | - Separated serial ports for DTU Tool (GUI app) and the device communication 25 | - Minor enhancements and newer API support 26 | -------------------------------------------------------------------------------- /code/modules/net_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | 检查sim卡和网络状态,如有异常尝试CFUN切换或重启 17 | """ 18 | import net 19 | import utime 20 | import _thread 21 | import checkNet 22 | from misc import Power 23 | 24 | 25 | class NetManager(object): 26 | RECONNECT_LOCK = _thread.allocate_lock() 27 | 28 | @classmethod 29 | def reconnect(cls, retry=3): 30 | with cls.RECONNECT_LOCK: 31 | for _ in range(retry): 32 | if cls.wait_connect(): 33 | return True 34 | cls.__cfun_switch() 35 | else: 36 | Power.powerRestart() 37 | return False 38 | 39 | @classmethod 40 | def wait_connect(cls, timeout=30): 41 | stage, state = checkNet.waitNetworkReady(timeout) 42 | # print('network status code: {}'.format((stage, state))) 43 | return stage == 3 and state == 1 44 | 45 | @classmethod 46 | def __cfun_switch(cls): 47 | cls.disconnect() 48 | utime.sleep_ms(200) 49 | cls.connect() 50 | 51 | @classmethod 52 | def connect(cls): 53 | return net.setModemFun(1, 0) == 0 54 | 55 | @classmethod 56 | def disconnect(cls): 57 | return net.setModemFun(0, 0) == 0 58 | -------------------------------------------------------------------------------- /docs/zh/DTU GUI 工具使用说明.md: -------------------------------------------------------------------------------- 1 | 2 | # DTU GUI 工具使用说明 3 | 4 | ## 1.基本概述 5 | 6 | 本文档主要介绍DTU GUI工具的使用。 7 | 8 | DTU GUI工具现阶段主要为客户开发调试使用,DTU GUI工具提供基础的查询与设置功能,以及模拟MCU测试和DTU模块数据收发,用户可使用USB to TTL模块连接PC与DTU。 9 | 10 | DTU GUI基于wxPython开发,现阶段已编译的dtu_gui.exe仅支持Windows系统, 11 | 用户在Linux/macOS配置Python环境并安装wxPython lib后可直接运行dtu_gui.py或自行编译对应版本的exe程序。 12 | 13 | ## 2. 运行DTU GUI 工具 14 | 15 | **双击打开DTU GUI工具** 16 | 17 | ![](./media/DTU_GUI_User_Guides/dtugui.jpg) 18 | 19 | **打开串口** 20 | 21 | ![](./media/DTU_GUI_User_Guides/gui_open_port_done.jpg) 22 | 23 | ## 3. DTU GUI 功能介绍 24 | 25 | ## 3.1 工具箱 26 | 27 | **目前工具箱的功能如下:** 28 | | **按键名** | **功能** | 29 | | ---------- | -------- | 30 | | **获取当前参数** | 获取DTU当前配置参数,并跳转到`参数配置`界面中显示具体参数 | 31 | | **保存所有设置参数并重启** | 将当前`参数配置`界面中配置参数写入DTU,并重启DTU | 32 | | **恢复出厂参数设置并重启** | 删除所有配置参数,恢复出厂参数,并重启DTU | 33 | | **查询IMEI号** | 获取DTU模组IMEI号 | 34 | | **查询本机号码** | 获取DTU中SIM卡手机号码 | 35 | | **查询信号强度** | 获取csq信号强度,信号强度值范围0 ~ 31,值越大表示信号强度越好 | 36 | | **设备重启** | 重启DTU设备 | 37 | 38 | ### 3.1.1 查询IMEI号: 39 | 40 | - 查询IMEI号: 41 | 42 | ![](./media/DTU_GUI_User_Guides/gui_get_imei.jpg) 43 | 44 | 在左侧串口数据显示框中以字符串格式显示出详细地串口数据,右侧命令消息框显示出查询获得的IMEI号。 45 | 46 | ### 3.1.1 获取DTU当前配置参数: 47 | 48 | 点击`获取当前参数`按钮后,立即跳到参数配置界面。 49 | ![](./media/DTU_GUI_User_Guides/gui_get_dtu_config.jpg) 50 | 51 | 点击交互界面可以看到工具和DTU具体交互串口信息。 52 | ![](./media/DTU_GUI_User_Guides/gui_get_dtu_config_uart_data.jpg) 53 | 54 | ## 3.3 导入配置参数 55 | 读取当前配置参数后,进入参数配置界面,可以根据实际需求修改配置(也可以不读取,直接填写配置)。 56 | ### 3.3.1基本参数配置 57 | 58 | ![](./media/DTU_GUI_User_Guides/gui_config_page1.jpg) 59 | 基本配置参数项如上图 60 | | **参数名** | **含义** | 61 | | ---------- | -------- | 62 | | 云平台通道类型 | 云平台选择,可选项:`阿里云`、`腾讯云`、`华为云`、`移远云`、`TCP私有云`、`MQTT私有云`| 63 | | 固件升级 | 是否开启固件OTA升级| 64 | | 脚本升级 | 是否开启项目脚本OTA升级| 65 | | 历史数据存储 | 当通信异常,DTU无法向云端发送数据时,将发送数据保存,待通信恢复正常后重新发送| 66 | | 串口号 | 外部MCU连接DTU串口号,可选项:`0`,`1`,`2`| 67 | | 波特率 | 串口波特率| 68 | | 数据位 | 奇偶校验| 69 | | 停止位 | 停止位长度,可选项:`1`,`2`| 70 | | 流控 | 硬件控制流,可选项:`FC_NONE`,`FC_HW`| 71 | | 控制485通信方向Pin | 串口发送数据之前和之后进行拉高拉低指定GPIO,用来指示485通信的方向。如`1`、 `2`代表`UART.GPIO1`、`UART.GPIO2`。 72 | 73 | ### 3.3.2 云参数配置 74 | 云参数配置项会根据基本`云平台通信类型`选择值变化。当`云平台通信类型`为阿里云时,云参数配置项如下: 75 | 76 | ![](./media/DTU_GUI_User_Guides/gui_aliyun_config.jpg) 77 | 78 | 当`云平台通信类型`为移远云时,云参数配置项如下: 79 | ![](./media/DTU_GUI_User_Guides/gui_quecthing_config.jpg) 80 | 81 | ## 3.4 数据发送框的格式要求 82 | 数据发送的格式与MCU和DTU通信格式一致。针对和云端通信协议的不同,模块和外部设备(如MCU)通信协议也会不同。当模块和云端通信使用TCP协议时,由于TCP和串口都是数据流的形式,所以直接透传数据,不做任何处理;当模块和云端通信使用MQTT协议时,为了区分不同的数据帧,模块的串口对外协议采用简单的数据帧: 83 | `,,"`。 84 | **注:移远云不支持Topic设置,``统一为`"0"`** 85 | 86 | **示例报文:** 87 | 88 | - 上行报文: 89 | 90 | `“1,6,abcedf”` 91 | 92 | - 下行报文: 93 | 94 | `“1,6,ijklmn”` 95 | 96 | 模块和外部设备(MCU)上行报文和下行报文都是采用字符串格式,数据项之间采用`,`相隔。 97 | ![](./media/DTU_GUI_User_Guides/gui_send_frame.jpg) 98 | -------------------------------------------------------------------------------- /docs/zh/DTU OTA 升级用户指导手册.md: -------------------------------------------------------------------------------- 1 | # DTU OTA 升级用户指导手册 2 | 3 | ## OTA升级 4 | 5 | > **固件升级只支持差分升级,不支持整包升级** 6 | 7 | ### 阿里云 8 | 9 | > **项目文件升级包,以修改项目代码文件后缀名为`.bin`的方式做成升级包,上传云端,可上传多个文件** 10 | 11 | #### 固件升级 12 | 13 | 1. 制作固件升级差分包(联系固件开发人员); 14 | 2. 创建OTA模块,以设备平台名称命名,如: `EC600N-CNLC`. 15 | 16 | ![](./media/aliyun_ota_fota_module.png) 17 | 18 | 3. 创建OTA升级包 19 | 20 | ![](./media/aliyun_ota_fota_upgrade_package.png) 21 | 22 | 4. 选择批量升级, 创建升级计划 23 | 24 | ![](./media/aliyun_ota_fota_plain.png) 25 | 26 | 5. 等待设备升级,查看升级结果 27 | 28 | + 当设备开启OTA升级和OTA自动升级,则等待设备升级完成,查看升级结果; 29 | + 当设备开启OTA升级,但未开启自动升级时,可通过在线调试模块下发`user_ota_action=1`的物模型设置指令,进行OTA升级。 30 | 31 | ![](./media/aliyun_ota_fota_upgrade_process.png) 32 | 33 | #### 项目升级 34 | 35 | 1. 创建OTA模块,以`settings.py`中`PROJECT_NAME`命名,如: `QuecPython-Tracker`. 36 | 37 | ![](./media/aliyun_ota_sota_module.png) 38 | 39 | 2. 将需要升级的项目文件后缀名修改为`.bin` 40 | 3. 创建OTA升级包 41 | + 此处需要在**推送给设备的自定义信息**中编写升级文件名对应的设备全路径文件名, 如: `{"files":{"common.bin":"/usr/modules/common.py","settings.bin":"/usr/settings.py","test_tracker.bin":"/usr/test_tracker.py"}}` 42 | 43 | ![](./media/aliyun_ota_sota_upgrade_package.png) 44 | 45 | 4. 选择批量升级, 创建升级计划 46 | 47 | ![](./media/aliyun_ota_sota_plain.png) 48 | 49 | 5. 等待设备升级,查看升级结果 50 | 51 | + 当设备开启OTA升级和OTA自动升级,则等待设备升级完成,查看升级结果; 52 | + 当设备开启OTA升级,但未开启自动升级时,可通过在线调试模块下发`user_ota_action=1`的物模型设置指令,进行OTA升级。 53 | 54 | ![](./media/aliyun_ota_sota_upgrade_process.png) 55 | 56 | ### 移远云 57 | 58 | > **项目文件升级包,建议以压缩包的形式打包多个项目文件上传云端** 59 | 60 | #### 固件升级 61 | 62 | 1. 制作固件升级差分包(联系固件开发人员); 63 | 2. 创建OTA升级模型,添加固件组件,MCU组件(用于项目升级) 64 | + 固件类型的组件标识以设备平台名称命名,如: `EC600N-CNLC`. 65 | + MCU类型的组件标识以`settings.py`中`PROJECT_NAME`命名,如: `QuecPython-Tracker`. 66 | 67 | ![](./media/quec_ota_model.png) 68 | 69 | 3. 创建固件版本升级包 70 | 71 | ![](./media/quec_ota_fota_version_package.png) 72 | 73 | 4. 创建固件升级计划 74 | 75 | ![](./media/quec_ota_fota_plain.png) 76 | 77 | 5. 等待设备升级,查看升级结果 78 | 79 | + 当设备开启OTA升级和OTA自动升级,则等待设备升级完成,查看升级结果; 80 | + 当设备开启OTA升级,但未开启自动升级时,可通过在线调试模块下发`user_ota_action=1`的物模型设置指令,进行OTA升级。 81 | 82 | ![](./media/quec_ota_fota_upgrade_process.png) 83 | 84 | #### 项目升级 85 | 86 | 1. 将项目文件打包成压缩包,打包指令: `tar -zcvf sotaFile.tar.gz *.py`; 87 | 2. 创建OTA升级模型,添加固件组件,MCU组件(用于项目升级) 88 | + 固件类型的组件标识以设备平台名称命名,如: `EC600N-CNLC`. 89 | + MCU类型的组件标识以`settings.py`中`PROJECT_NAME`命名,如: `QuecPython-Tracker`. 90 | 91 | ![](./media/quec_ota_model.png) 92 | 93 | 3. 如果在创建OTA模型时未创建MCU组件,可在模型中添加组件 94 | 95 | ![](./media/quec_ota_model_add_mcu_module.png) 96 | 97 | 4. 创建项目版本升级包 98 | 99 | ![](./media/quec_ota_sota_version_package.png) 100 | 101 | 4. 创建项目升级计划 102 | 103 | ![](./media/quec_ota_sota_plain.png) 104 | 105 | 5. 等待设备升级,查看升级结果 106 | 107 | + 当设备开启OTA升级和OTA自动升级,则等待设备升级完成,查看升级结果; 108 | + 当设备开启OTA升级,但未开启自动升级时,可通过在线调试模块下发`user_ota_action=1`的物模型设置指令,进行OTA升级。 109 | 110 | ![](./media/quec_ota_sota_upgrade_process.png) 111 | -------------------------------------------------------------------------------- /code/modules/logging.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :logging.py 20 | @author :Jack.Sun@quectel.com 21 | @brief :print log message 22 | @version :0.1 23 | @date :2022-05-20 16:26:41 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | import utime 28 | 29 | class Logger: 30 | def __init__(self, name): 31 | self.name = name 32 | self.__debug = True 33 | self.__level = "debug" 34 | self.__level_code = { 35 | "debug": 0, 36 | "info": 1, 37 | "warn": 2, 38 | "error": 3, 39 | "critical": 4, 40 | } 41 | 42 | def get_debug(self): 43 | return self.__debug 44 | 45 | def set_debug(self, debug): 46 | if isinstance(debug, bool): 47 | self.__debug = debug 48 | return True 49 | return False 50 | 51 | def get_level(self): 52 | return self.__level 53 | 54 | def set_level(self, level): 55 | if self.__level_code.get(level) is not None: 56 | self.__level = level 57 | return True 58 | return False 59 | 60 | def log(self, name, level, *message): 61 | if self.__debug is False: 62 | if self.__level_code[level] < self.__level_code[self.__level]: 63 | return 64 | 65 | if hasattr(utime, "strftime"): 66 | print( 67 | "[{}]".format(utime.strftime("%Y-%m-%d %H:%M:%S")), # type: ignore 68 | "[{}]".format(name), 69 | "[{}]".format(level), 70 | *message 71 | ) 72 | else: 73 | t = utime.localtime() 74 | print( 75 | "[{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}]".format(*t), 76 | "[{}]".format(name), 77 | "[{}]".format(level), 78 | *message 79 | ) 80 | 81 | def critical(self, *message): 82 | self.log(self.name, "critical", *message) 83 | 84 | def error(self, *message): 85 | self.log(self.name, "error", *message) 86 | 87 | def warn(self, *message): 88 | self.log(self.name, "warn", *message) 89 | 90 | def info(self, *message): 91 | self.log(self.name, "info", *message) 92 | 93 | def debug(self, *message): 94 | self.log(self.name, "debug", *message) 95 | 96 | 97 | def getLogger(name): 98 | return Logger(name) 99 | -------------------------------------------------------------------------------- /code/settings_user.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class UserConfig(object): 17 | system_config = { 18 | "cloud": "aliyun", 19 | "usr_config": False, 20 | "base_function": 21 | { 22 | "logger": True, 23 | "offline_storage": True, 24 | "fota":True, 25 | "sota":True 26 | }, 27 | } 28 | usr_config = {} 29 | aliyun_config = { 30 | "server": "gzsi5zT5fH3.iot-as-mqtt.cn-shanghai.aliyuncs.com", 31 | "DK": "dtu_device1", 32 | "PK": "gzsi5zT5fH3", 33 | "DS": "173f006cab770615346978583ac430c0", 34 | "PS": "D07Ujh1RvKAs6KEY", 35 | "burning_method": 1, 36 | "keep_alive": 300, 37 | "clean_session": False, 38 | "qos": 1, 39 | "client_id":"", 40 | "subscribe": {"0": "/gzsi5zT5fH3/dtu_device1/user/get"}, 41 | "publish": {"0": "/gzsi5zT5fH3/dtu_device1/user/update"} 42 | } 43 | txyun_config = { 44 | "DK": "dtu_device1", 45 | "PK": "I81T7DUSFF", 46 | "DS": "wF+b5NwEHI53crHmOqdyQA==", 47 | "PS": "", 48 | "burning_method": 1, 49 | "keep_alive": 300, 50 | "clean_session": False, 51 | "qos": 1, 52 | "client_id":"", 53 | "subscribe": {"0": "I81T7DUSFF/dtu_device1/control"}, 54 | "publish": {"0": "I81T7DUSFF/dtu_device1/event"} 55 | } 56 | hwyun_config = { 57 | "server": "a15fbbd7ce.iot-mqtts.cn-north-4.myhuaweicloud.com", 58 | "port": "1883", 59 | "DK": "625132b420cfa22b94c54613_dtu_device1_id", 60 | "PK": "", 61 | "DS": "a306255686a71e56ad53965fc2771bf8", 62 | "PS": "", 63 | "keep_alive": 300, 64 | "clean_session": False, 65 | "qos": 1, 66 | "subscribe": {"0": "$oc/devices/625132b420cfa22b94c54613_dtu_device1_id/sys/messages/down"}, 67 | "publish": {"0": "$oc/devices/625132b420cfa22b94c54613_dtu_device1_id/sys/messages/up"} 68 | } 69 | tcp_private_cloud_config = { 70 | "ip_type":"IPv4", 71 | "server": "a15fbbd7ce.iot-mqtts.cn-north-4.myhuaweicloud.com", 72 | "port": "1883", 73 | "keep_alive": 5 74 | } 75 | mqtt_private_cloud_config = { 76 | "server": "a15fbbd7ce.iot-mqtts.cn-north-4.myhuaweicloud.com", 77 | "port": "1883", 78 | "client_id": "test_mqtt", 79 | "clean_session": False, 80 | "qos": "0", 81 | "keep_alive": 300, 82 | "subscribe": {"0": "oc/devices/625132b420cfa22b94c54613_dtu_device1_id/sys/messages/down"}, 83 | "publish": {"0": "oc/devices/625132b420cfa22b94c54613_dtu_device1_id/sys/messages/up"} 84 | } 85 | uart_config = { 86 | "port" : "2", 87 | "baudrate": "115200", 88 | "databits": "8", 89 | "parity": "0", 90 | "stopbits": "1", 91 | "flowctl": "0", 92 | "rs485_direction_pin": "" 93 | } -------------------------------------------------------------------------------- /code/dtu_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "system_config": { 3 | "cloud": "mqtt_private_cloud", 4 | "usr_config": false, 5 | "base_function": { 6 | "logger": true, 7 | "offline_storage": true, 8 | "fota": true, 9 | "sota": true 10 | } 11 | }, 12 | "usr_config": { 13 | "filter_mask": "" 14 | }, 15 | "aliyun_config": { 16 | "server": "gzsi5zT5fH3.iot-as-mqtt.cn-shanghai.aliyuncs.com", 17 | "DK": "dtu_device1", 18 | "PK": "gzsi5zT5fH3", 19 | "DS": "173f006cab770615346978583ac430c0", 20 | "PS": "D07Ujh1RvKAs6KEY", 21 | "burning_method": 1, 22 | "keep_alive": 300, 23 | "clean_session": false, 24 | "qos": 1, 25 | "client_id": "", 26 | "subscribe": { 27 | "0": "/gzsi5zT5fH3/dtu_device1/user/get" 28 | }, 29 | "publish": { 30 | "0": "/gzsi5zT5fH3/dtu_device1/user/update" 31 | } 32 | }, 33 | "txyun_config": { 34 | "DK": "dtu_device1", 35 | "PK": "I81T7DUSFF", 36 | "DS": "wF+b5NwEHI53crHmOqdyQA==", 37 | "PS": "", 38 | "burning_method": 1, 39 | "keep_alive": 300, 40 | "clean_session": false, 41 | "qos": 1, 42 | "client_id": "", 43 | "subscribe": { 44 | "0": "I81T7DUSFF/dtu_device1/control" 45 | }, 46 | "publish": { 47 | "0": "I81T7DUSFF/dtu_device1/event" 48 | } 49 | }, 50 | "hwyun_config": { 51 | "server": "a15fbbd7ce.iot-mqtts.cn-north-4.myhuaweicloud.com", 52 | "port": "1883", 53 | "DK": "625132b420cfa22b94c54613_dtu_device1_id", 54 | "PK": "", 55 | "DS": "a306255686a71e56ad53965fc2771bf8", 56 | "PS": "", 57 | "keep_alive": 300, 58 | "clean_session": false, 59 | "qos": 1, 60 | "subscribe": { 61 | "0": "$oc/devices/625132b420cfa22b94c54613_dtu_device1_id/sys/messages/down" 62 | }, 63 | "publish": { 64 | "0": "$oc/devices/625132b420cfa22b94c54613_dtu_device1_id/sys/messages/up" 65 | } 66 | }, 67 | "tcp_private_cloud_config": { 68 | "ip_type": "IPv4", 69 | "server": "220.180.239.212", 70 | "port": "18011", 71 | "keep_alive": 5 72 | }, 73 | "mqtt_private_cloud_config": { 74 | "server": "test.mosquitto.org", 75 | "port": "1883", 76 | "client_id": "2ff01e56-e02a-4919-935d-de1f2677b4f9", 77 | "username": "", 78 | "password": "", 79 | "clean_session": false, 80 | "qos": "0", 81 | "keep_alive": 100, 82 | "subscribe": { 83 | "0": "/quedtu123_test/dev1/down" 84 | }, 85 | "publish": { 86 | "0": "/quedtu123_test/dev1/up" 87 | } 88 | }, 89 | "uart_config": { 90 | "port": "2", 91 | "baudrate": "115200", 92 | "databits": "8", 93 | "parity": "0", 94 | "stopbits": "1", 95 | "flowctl": "0", 96 | "rs485_direction_pin": "" 97 | }, 98 | "uart_secondary_config": { 99 | "port": "1", 100 | "baudrate": "115200", 101 | "databits": "8", 102 | "parity": "0", 103 | "stopbits": "1", 104 | "flowctl": "0", 105 | "rs485_direction_pin": "" 106 | } 107 | } -------------------------------------------------------------------------------- /code/modules/serial.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :serial.py 20 | @author :Chavis.Chen (chavis.chen@quectel.com) 21 | @brief :serial Tx & Rx implementations 22 | @version :0.1 23 | @date :2022-06-15 15:25:21 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | from machine import UART 28 | from machine import Timer 29 | from queue import Queue 30 | from usr.modules.logging import getLogger 31 | 32 | class Serial(object): 33 | def __init__(self, 34 | uart, 35 | buadrate = 115200, 36 | databits = 8, 37 | parity = 0, 38 | stopbits = 1, 39 | flowctl = 0, 40 | rs485_direction_pin = ""): 41 | 42 | uart_port = getattr(UART, "UART%d" % int(uart)) 43 | self._uart = UART(uart_port, buadrate, databits, parity, stopbits, flowctl) 44 | # init rs458 rx/tx pin 45 | if rs485_direction_pin != "": 46 | rs485_pin = getattr(UART, "GPIO%d" % int(rs485_direction_pin)) 47 | self._uart.control_485(rs485_pin, 1) 48 | self._queue = Queue(maxsize = 1) 49 | self._timer = Timer(Timer.Timer1) 50 | self._log = getLogger(__name__) 51 | 52 | self._uart.set_callback(self._uart_cb) 53 | self.log_enable(False) 54 | 55 | def _uart_cb(self, args): 56 | self._log.debug("_uart_cb called with args:", args) 57 | if self._queue.size() == 0: # type: ignore 58 | self._log.debug("_uart_cb send a signal") 59 | self._queue.put(None) 60 | 61 | def _timer_cb(self, args): 62 | self._log.debug("_timer_cb called with args:", args) 63 | if self._queue.size() == 0: # type: ignore 64 | self._log.debug("_timer_cb send a signal") 65 | self._queue.put(None) 66 | 67 | def log_enable(self, en): 68 | if not isinstance(en, bool): 69 | return False 70 | 71 | if en: 72 | self._log.set_level("debug") 73 | else: 74 | self._log.set_level("critical") 75 | 76 | self._log.set_debug(en) 77 | return True 78 | 79 | def write(self, data): 80 | self._uart.write(data) 81 | 82 | def read(self, nbytes, timeout = 0): 83 | if nbytes == 0: 84 | return '' 85 | 86 | if self._uart.any() == 0 and timeout != 0: 87 | timer_started = False 88 | if timeout > 0: # < 0 for wait forever 89 | self._log.debug("start a timeout timer:", timeout) 90 | self._timer.start(period = timeout, mode = Timer.ONE_SHOT, callback = self._timer_cb) 91 | timer_started = True 92 | self._log.debug("wait for a signal") 93 | self._queue.get() 94 | if timer_started: 95 | self._timer.stop() 96 | 97 | r_data = self._uart.read(min(nbytes, self._uart.any())).decode() 98 | if self._queue.size(): # type: ignore 99 | self._log.debug("clean an extra signal") 100 | self._queue.get() 101 | 102 | return r_data -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | # QuecPython DTU 解决方案 2 | 3 | 中文 | [English](readme.md) 4 | 5 | 欢迎来到 QuecPython DTU 解决方案仓库!本仓库提供了一个全面的解决方案,用于使用 QuecPython DTU 设备应用程序。 6 | 7 | ## 目录 8 | 9 | - [介绍](#介绍) 10 | - [功能](#功能) 11 | - [快速开始](#快速开始) 12 | - [先决条件](#先决条件) 13 | - [安装](#安装) 14 | - [运行应用程序](#运行应用程序) 15 | - [目录结构](#目录结构) 16 | - [使用](#使用) 17 | - [贡献](#贡献) 18 | - [许可证](#许可证) 19 | - [支持](#支持) 20 | 21 | ## 介绍 22 | 23 | QuecPython 推出了针对 DTU 的解决方案,包括多种协议的数据传输(TCP/UDP/MQTT/HTTP等)、常见云平台的对接、支持使用上位机对 DTU 进行参数配置等功能。 24 | 25 | ![DTU](./docs/zh/media/DP-DTU-Q600.png) 26 | 27 | ## 功能 28 | 29 | - **多种协议的数据传输**:支持TCP/UDP/MQTT/HTTP等协议的数据传输,可配置为命令模式或透传模式。 30 | - **常见云平台的对接**:支持阿里云、腾讯云、华为云、AWS等云平台的对接。 31 | - **参数配置与存储**:支持使用专门的 DTU 工具对设备做参数配置,并持久化存储于设备中。 32 | 33 | ## 快速开始 34 | 35 | ### 先决条件 36 | 37 | 在开始之前,请确保您具备以下先决条件: 38 | 39 | - **硬件**: 40 | - QuecPython 开发板套件或 DTU 设备 41 | > 点击查看DTU开发板的[原理图](https://images.quectel.com/python/2024/10/DP-DTU-Q600-EVB-V1.3-SCH.pdf)和[丝印图](https://images.quectel.com/python/2024/10/DP-DTU-Q600-EVB-V1.3-SilkScreen.pdf)文档。 42 | - USB 数据线(USB-A 转 USB-C) 43 | - 电脑(Windows 7、Windows 10 或 Windows 11) 44 | 45 | - **软件**: 46 | - QuecPython 模块的 USB 驱动 47 | - QPYcom 调试工具 48 | - QuecPython 固件及相关软件资源 49 | - Python 文本编辑器(例如,[VSCode](https://code.visualstudio.com/)、[Pycharm](https://www.jetbrains.com/pycharm/download/)) 50 | 51 | ### 安装 52 | 53 | 1. **克隆仓库**: 54 | ```bash 55 | git clone https://github.com/QuecPython/solution-DTU.git 56 | cd solution-DTU 57 | ``` 58 | 59 | 2. **烧录固件**: 60 | 按照[说明](https://python.quectel.com/doc/Application_guide/zh/dev-tools/QPYcom/qpycom-dw.html#Download-Firmware)将固件烧录到开发板上。 61 | 62 | ### 运行应用程序 63 | 64 | 1. **连接硬件**: 65 | - 使用 USB 数据线将开发板连接到计算机的 USB 端口。 66 | 67 | 2. **将代码下载到设备**: 68 | - 启动 QPYcom 调试工具。 69 | - 将数据线连接到计算机。 70 | - 按下开发板上的 **PWRKEY** 按钮启动设备。 71 | - 按照[说明](https://python.quectel.com/doc/Application_guide/zh/dev-tools/QPYcom/qpycom-dw.html#Download-Script)将 `code` 文件夹中的所有文件导入到模块的文件系统中,保留目录结构。 72 | 73 | 3. **运行应用程序**: 74 | - 选择 `File` 选项卡。 75 | - 选择 `dtu.py` 脚本。 76 | - 右键单击并选择 `Run` 或使用`运行`快捷按钮执行脚本。 77 | 78 | ## 目录结构 79 | 80 | ```plaintext 81 | solution-DTU/ 82 | ├── CHANGELOG.md 83 | ├── code/ 84 | │   ├── __init__.py 85 | │   ├── dtu.py 86 | │   ├── dtu_config.json 87 | │   ├── dtu_transaction.py 88 | │   ├── modules/ 89 | │   │   ├── __init__.py 90 | │   │   ├── aliyunIot.py 91 | │   │   ├── common.py 92 | │   │   ├── history.py 93 | │   │   ├── huawei_cloud.py 94 | │   │   ├── logging.py 95 | │   │   ├── mqttIot.py 96 | │   │   ├── net_manager.py 97 | │   │   ├── remote.py 98 | │   │   ├── serial.py 99 | │   │   ├── socketIot.py 100 | │   │   └── txyunIot.py 101 | │   ├── settings.py 102 | │   └── settings_user.py 103 | ├── docs/ 104 | │   ├── en/ 105 | │   │   ├── DTU_GUI_Tool_User_Guide.md 106 | │   │   ├── DTU_OTA_Upgrade_User_Guide.md 107 | │   │   ├── DTU_Solution_Development_Guide.md 108 | │   │   └── media/ 109 | │   └── zh/ 110 | │   ├── DTU GUI 工具使用说明.md 111 | │   ├── DTU OTA 升级用户指导手册.md 112 | │   ├── DTU 公版方案用户开发手册.md 113 | │   └── media/ 114 | ├── dtu_tool/ 115 | │   ├── build.sh 116 | │   ├── cloud_config.py 117 | │   ├── dtu_tool.py 118 | │   ├── LICENSE 119 | │   ├── quectel.ico 120 | │   └── translation/ 121 | │   ├── __init__.py 122 | │   ├── language 123 | │   └── zh_CN.json 124 | ├── LICENSE 125 | ├── readme.md 126 | └── readme_zh.md 127 | ``` 128 | 129 | ## 使用 130 | 131 | - [DTU 公版方案用户开发手册](./docs/zh/DTU%20公版方案用户开发手册.md) 132 | - [DTU OTA 升级用户指导手册](./docs/zh/DTU%20OTA%20升级用户指导手册.md) 133 | - [DTU GUI 工具使用说明](./docs/zh/DTU%20GUI%20工具使用说明.md) 134 | 135 | ## 贡献 136 | 137 | 我们欢迎对本项目的改进做出贡献!请按照以下步骤进行贡献: 138 | 139 | 1. Fork 此仓库。 140 | 2. 创建一个新分支(`git checkout -b feature/your-feature`)。 141 | 3. 提交您的更改(`git commit -m 'Add your feature'`)。 142 | 4. 推送到分支(`git push origin feature/your-feature`)。 143 | 5. 打开一个 Pull Request。 144 | 145 | ## 许可证 146 | 147 | 本项目使用 Apache 许可证。详细信息请参阅 [LICENSE](LICENSE) 文件。 148 | 149 | ## 支持 150 | 151 | 如果您有任何问题或需要支持,请参阅 [QuecPython 文档](https://python.quectel.com/doc) 或在本仓库中打开一个 issue。 152 | -------------------------------------------------------------------------------- /code/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import uos 16 | import ql_fs 17 | import ujson 18 | import modem 19 | import _thread 20 | 21 | from usr.modules.common import Singleton 22 | from usr.modules.common import option_lock 23 | from usr.settings_user import UserConfig 24 | from usr.modules.logging import getLogger 25 | 26 | log = getLogger(__name__) 27 | 28 | PROJECT_NAME = "QuecPython-Dtu" 29 | PROJECT_VERSION = "3.0.0" 30 | 31 | DEVICE_FIRMWARE_NAME = uos.uname()[0].split("=")[1] 32 | DEVICE_FIRMWARE_VERSION = modem.getDevFwVersion() 33 | 34 | _settings_lock = _thread.allocate_lock() 35 | 36 | 37 | class Settings(Singleton): 38 | 39 | def __init__(self, settings_file="/usr/dtu_config.json"): 40 | self.settings_file = settings_file 41 | self.current_settings = {} 42 | self.init() 43 | 44 | def __init_config(self): 45 | try: 46 | self.current_settings = {k: v for k, v in UserConfig.__dict__.items() if not k.startswith("_")} 47 | return True 48 | except: 49 | return False 50 | 51 | def __read_config(self): 52 | if ql_fs.path_exists(self.settings_file): 53 | with open(self.settings_file, "r") as f: 54 | self.current_settings = ujson.load(f) 55 | return True 56 | return False 57 | 58 | def __set_config(self, opt, val): 59 | if opt in ["fota", "sota", "offline_storage"]: 60 | self.current_settings["system_config"]["base_function"][opt] = val 61 | return True 62 | elif opt in [ 63 | "uart_config", 64 | "aliyun_config", 65 | "txyun_config", 66 | "hwyun_config", 67 | "tcp_private_cloud_config", 68 | "mqtt_private_cloud_config" 69 | ]: 70 | if not isinstance(val, dict): 71 | return False 72 | self.current_settings[opt] = val 73 | return True 74 | elif opt in ["cloud"]: 75 | if not isinstance(val, str): 76 | return False 77 | self.current_settings["system_config"][opt] = val 78 | return True 79 | 80 | return False 81 | 82 | def __save_config(self): 83 | try: 84 | with open(self.settings_file, "w") as f: 85 | ujson.dump(self.current_settings, f) 86 | return True 87 | except: 88 | return False 89 | 90 | def __remove_config(self): 91 | try: 92 | uos.remove(self.settings_file) 93 | return True 94 | except: 95 | return False 96 | 97 | def __get_config(self): 98 | return self.current_settings 99 | 100 | @option_lock(_settings_lock) 101 | def init(self): 102 | if self.__read_config() is False: 103 | if self.__init_config(): 104 | return self.__save_config() 105 | return False 106 | 107 | @option_lock(_settings_lock) 108 | def get(self): 109 | return self.__get_config() 110 | 111 | @option_lock(_settings_lock) 112 | def set(self, opt, val): 113 | return self.__set_config(opt, val) 114 | 115 | @option_lock(_settings_lock) 116 | def save(self): 117 | return self.__save_config() 118 | 119 | @option_lock(_settings_lock) 120 | def remove(self): 121 | return self.__remove_config() 122 | 123 | @option_lock(_settings_lock) 124 | def reset(self): 125 | if self.__remove_config(): 126 | if self.__init_config(): 127 | return self.__save_config() 128 | return False 129 | 130 | def set_multi(self, **kwargs): 131 | for k in self.current_settings.keys(): 132 | if k in kwargs: 133 | try: 134 | if not self.__set_config(k, kwargs[k]): 135 | raise Exception("Set parameter error") 136 | except: 137 | return False 138 | return True 139 | 140 | 141 | settings = Settings() -------------------------------------------------------------------------------- /code/modules/history.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :history.py 20 | @author :Jack Sun (jack.sun@quectel.com) 21 | @brief :Store data that fails to be sent, and send when communication is normal 22 | @version :0.1 23 | @date :2022-05-20 16:25:07 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | 28 | import uos 29 | import ql_fs 30 | import ujson 31 | import _thread 32 | 33 | from usr.modules.logging import getLogger 34 | from usr.modules.common import Singleton, option_lock 35 | 36 | log = getLogger(__name__) 37 | 38 | _history_lock = _thread.allocate_lock() 39 | 40 | 41 | class History(Singleton): 42 | """This class is for manage history file.""" 43 | 44 | def __init__(self, history_file="/usr/dtu_data.hist", max_hist_num=100): 45 | """ 46 | Parameter: 47 | history_file: filename include full path 48 | max_hist_num: history data list max size 49 | """ 50 | self.__history = history_file 51 | self.__max_hist_num = max_hist_num 52 | 53 | def __read_hist(self): 54 | """Read history file info 55 | 56 | Return: 57 | data format: 58 | { 59 | "data": [ 60 | { 61 | "xxx": "wwww" 62 | } 63 | ] 64 | } 65 | """ 66 | res = {"data": []} 67 | if ql_fs.path_exists(self.__history): 68 | with open(self.__history, "r") as f: 69 | try: 70 | hist_data = ujson.load(f) 71 | if isinstance(hist_data, dict): 72 | res["data"] = hist_data.get("data", []) 73 | except Exception: 74 | pass 75 | return res 76 | 77 | def __write_hist(self, data): 78 | """Write data to history file 79 | 80 | Parameter: 81 | data format: 82 | { 83 | "data": [ 84 | { 85 | "xxx": "wwww" 86 | } 87 | ] 88 | } 89 | 90 | Return: 91 | True: Success 92 | False: Falied 93 | """ 94 | try: 95 | with open(self.__history, "w") as f: 96 | ujson.dump(data, f) 97 | return True 98 | except: 99 | return False 100 | 101 | @option_lock(_history_lock) 102 | def read(self): 103 | """Read history info 104 | 105 | Return: 106 | data format: 107 | { 108 | "data": [ 109 | { 110 | "switch": True, 111 | "energy": 100, 112 | "gps": ["$GPRMCx,x,x,x", "$GPGGAx,x,x,x"], 113 | }, 114 | { 115 | "switch": True, 116 | "energy": 100, 117 | "non_gps": ["LBS"], 118 | }, 119 | ], 120 | } 121 | """ 122 | res = self.__read_hist() 123 | self.__write_hist({"data": []}) 124 | return res 125 | 126 | @option_lock(_history_lock) 127 | def write(self, data): 128 | """ 129 | Data Format For Write History: 130 | 131 | [ 132 | { 133 | "switch": True, 134 | "energy": 100, 135 | "gps": ["$GPRMCx,x,x,x", "$GPGGAx,x,x,x"], 136 | }, 137 | { 138 | "switch": True, 139 | "energy": 100, 140 | "non_gps": ["LBS"], 141 | }, 142 | ] 143 | """ 144 | res = self.__read_hist() 145 | res["data"].extend(data) 146 | if len(res["data"]) > self.__max_hist_num: 147 | res["data"] = res["data"][self.__max_hist_num * -1:] 148 | return self.__write_hist(res) 149 | 150 | @option_lock(_history_lock) 151 | def clean(self): 152 | try: 153 | uos.remove(self.__history) 154 | return True 155 | except: 156 | return False 157 | 158 | def update(self, observable, *args, **kwargs): 159 | return self.write(list(args[1:])) 160 | -------------------------------------------------------------------------------- /docs/en/DTU_OTA_Upgrade_User_Guide.md: -------------------------------------------------------------------------------- 1 | # DTU OTA Upgrade User Guide 2 | 3 | ## OTA Upgrade 4 | 5 | > **Firmware upgrades only support differential upgrades, not full package upgrades** 6 | 7 | ### Aliyun 8 | 9 | > **Project file upgrade package: create the upgrade package by changing the file extension of the project code file to `.bin`, upload it to the cloud, and multiple files can be uploaded** 10 | 11 | #### Firmware Upgrade 12 | 13 | 1. Create a differential firmware upgrade package (contact firmware developers); 14 | 2. Create an OTA module, named after the device platform name, e.g., `EC600N-CNLC`. 15 | 16 | ![](./media/aliyun_ota_fota_module.png) 17 | 18 | 3. Create an OTA upgrade package 19 | 20 | ![](./media/aliyun_ota_fota_upgrade_package.png) 21 | 22 | 4. Select batch upgrade, create an upgrade plan 23 | 24 | ![](./media/aliyun_ota_fota_plain.png) 25 | 26 | 5. Wait for the device to upgrade and check the upgrade results 27 | 28 | + If the device has OTA upgrade and automatic OTA upgrade enabled, wait for the device to complete the upgrade and check the upgrade results; 29 | + If the device has OTA upgrade enabled but automatic upgrade is not enabled, you can issue the `user_ota_action=1` model setting command through the online debugging module to perform the OTA upgrade. 30 | 31 | ![](./media/aliyun_ota_fota_upgrade_process.png) 32 | 33 | #### Project Upgrade 34 | 35 | 1. Create an OTA module, named after `PROJECT_NAME` in `settings.py`, e.g., `QuecPython-Tracker`. 36 | 37 | ![](./media/aliyun_ota_sota_module.png) 38 | 39 | 2. Change the file extension of the project files to `.bin` 40 | 3. Create an OTA upgrade package 41 | + In the **Custom Information Pushed to Device** section, write the upgrade file name corresponding to the full path file name on the device, e.g., `{"files":{"common.bin":"/usr/modules/common.py","settings.bin":"/usr/settings.py","test_tracker.bin":"/usr/test_tracker.py"}}` 42 | 43 | ![](./media/aliyun_ota_sota_upgrade_package.png) 44 | 45 | 4. Select batch upgrade, create an upgrade plan 46 | 47 | ![](./media/aliyun_ota_sota_plain.png) 48 | 49 | 5. Wait for the device to upgrade and check the upgrade results 50 | 51 | + If the device has OTA upgrade and automatic OTA upgrade enabled, wait for the device to complete the upgrade and check the upgrade results; 52 | + If the device has OTA upgrade enabled but automatic upgrade is not enabled, you can issue the `user_ota_action=1` model setting command through the online debugging module to perform the OTA upgrade. 53 | 54 | ![](./media/aliyun_ota_sota_upgrade_process.png) 55 | 56 | ### Quectel Cloud 57 | 58 | > **For project file upgrade packages, it is recommended to package multiple project files into a compressed file and upload it to the cloud** 59 | 60 | #### Firmware Upgrade 61 | 62 | 1. Create a differential firmware upgrade package (contact firmware developers); 63 | 2. Create an OTA upgrade model, add firmware components, and MCU components (for project upgrades) 64 | + The identifier of the firmware type component is named after the device platform name, e.g., `EC600N-CNLC`. 65 | + The identifier of the MCU type component is named after `PROJECT_NAME` in `settings.py`, e.g., `QuecPython-Tracker`. 66 | 67 | ![](./media/quec_ota_model.png) 68 | 69 | 3. Create a firmware version upgrade package 70 | 71 | ![](./media/quec_ota_fota_version_package.png) 72 | 73 | 4. Create a firmware upgrade plan 74 | 75 | ![](./media/quec_ota_fota_plain.png) 76 | 77 | 5. Wait for the device to upgrade and check the upgrade results 78 | 79 | + If the device has OTA upgrade and automatic OTA upgrade enabled, wait for the device to complete the upgrade and check the upgrade results; 80 | + If the device has OTA upgrade enabled but automatic upgrade is not enabled, you can issue the `user_ota_action=1` model setting command through the online debugging module to perform the OTA upgrade. 81 | 82 | ![](./media/quec_ota_fota_upgrade_process.png) 83 | 84 | #### Project Upgrade 85 | 86 | 1. Package the project files into a compressed file, packaging command: `tar -zcvf sotaFile.tar.gz *.py`; 87 | 2. Create an OTA upgrade model, add firmware components, and MCU components (for project upgrades) 88 | + The identifier of the firmware type component is named after the device platform name, e.g., `EC600N-CNLC`. 89 | + The identifier of the MCU type component is named after `PROJECT_NAME` in `settings.py`, e.g., `QuecPython-Tracker`. 90 | 91 | ![](./media/quec_ota_model.png) 92 | 93 | 3. If MCU components were not created when creating the OTA model, they can be added in the model 94 | 95 | ![](./media/quec_ota_model_add_mcu_module.png) 96 | 97 | 4. Create a project version upgrade package 98 | 99 | ![](./media/quec_ota_sota_version_package.png) 100 | 101 | 4. Create a project upgrade plan 102 | 103 | ![](./media/quec_ota_sota_plain.png) 104 | 105 | 5. Wait for the device to upgrade and check the upgrade results 106 | 107 | + If the device has OTA upgrade and automatic OTA upgrade enabled, wait for the device to complete the upgrade and check the upgrade results; 108 | + If the device has OTA upgrade enabled but automatic upgrade is not enabled, you can issue the `user_ota_action=1` model setting command through the online debugging module to perform the OTA upgrade. 109 | 110 | ![](./media/quec_ota_sota_upgrade_process.png) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuecPython DTU Solution 2 | 3 | [中文](readme_zh.md) | English 4 | 5 | Welcome to the QuecPython DTU Solution repository! This repository provides a comprehensive solution for developing DTU device applications using QuecPython. 6 | 7 | ## Table of Contents 8 | 9 | - [Introduction](#introduction) 10 | - [Features](#features) 11 | - [Getting Started](#getting-started) 12 | - [Prerequisites](#prerequisites) 13 | - [Installation](#installation) 14 | - [Running the Application](#running-the-application) 15 | - [Directory Structure](#directory-structure) 16 | - [Usage](#usage) 17 | - [Contributing](#contributing) 18 | - [License](#license) 19 | - [Support](#support) 20 | 21 | ## Introduction 22 | 23 | QuecPython has launched a solution for DTU, including multi-protocol data transmission (TCP/UDP/MQTT/HTTP, etc.), integration with common cloud platforms, and support for parameter configuration of DTU using upper computer tools. 24 | 25 | ![DTU](./docs/en/media/DP-DTU-Q600.png) 26 | 27 | ## Features 28 | 29 | - **Multi-Protocol Data Transmission**: Supports data transmission via TCP/UDP/MQTT/HTTP protocols, configurable as command mode or transparent transmission mode. 30 | - **Integration with Common Cloud Platforms**: Supports integration with Alibaba Cloud, Tencent Cloud, Huawei Cloud, AWS, and other cloud platforms. 31 | - **Parameter Configuration and Storage**: Supports parameter configuration of the device using a dedicated DTU tool, with persistent storage on the device. 32 | 33 | ## Getting Started 34 | 35 | ### Prerequisites 36 | 37 | Before you begin, ensure you have the following prerequisites: 38 | 39 | - **Hardware**: 40 | - QuecPython development board kit or DTU device. 41 | > Click for DTU EVB's [schematic](https://images.quectel.com/python/2024/10/DP-DTU-Q600-EVB-V1.3-SCH.pdf) and [silk screen](https://images.quectel.com/python/2024/10/DP-DTU-Q600-EVB-V1.3-SilkScreen.pdf) documents. 42 | - USB Data Cable (USB-A to USB-C). 43 | - PC (Windows 7, Windows 10, or Windows 11). 44 | 45 | - **Software**: 46 | - USB driver for the QuecPython module. 47 | - QPYcom debugging tool. 48 | - QuecPython firmware and related software resources. 49 | - Python text editor (e.g., [VSCode](https://code.visualstudio.com/), [Pycharm](https://www.jetbrains.com/pycharm/download/)). 50 | 51 | ### Installation 52 | 53 | 1. **Clone the Repository**: 54 | ```bash 55 | git clone https://github.com/QuecPython/solution-DTU.git 56 | cd solution-DTU 57 | ``` 58 | 59 | 2. **Flash the Firmware**: 60 | 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. 61 | 62 | ### Running the Application 63 | 64 | 1. **Connect the Hardware**: 65 | - Use a USB data cable to connect the development board to the computer's USB port. 66 | 67 | 2. **Download Code to the Device**: 68 | - Launch the QPYcom debugging tool. 69 | - Connect the data cable to the computer. 70 | - Press the **PWRKEY** button on the development board to start the device. 71 | - 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. 72 | 73 | 3. **Run the Application**: 74 | - Select the `File` tab. 75 | - Select the `dtu.py` script. 76 | - Right-click and select `Run` or use the run shortcut button to execute the script. 77 | 78 | ## Directory Structure 79 | 80 | ```plaintext 81 | solution-DTU/ 82 | ├── CHANGELOG.md 83 | ├── code/ 84 | │ ├── __init__.py 85 | │ ├── dtu.py 86 | │ ├── dtu_config.json 87 | │ ├── dtu_transaction.py 88 | │ ├── modules/ 89 | │ │ ├── __init__.py 90 | │ │ ├── aliyunIot.py 91 | │ │ ├── common.py 92 | │ │ ├── history.py 93 | │ │ ├── huawei_cloud.py 94 | │ │ ├── logging.py 95 | │ │ ├── mqttIot.py 96 | │ │ ├── net_manager.py 97 | │ │ ├── remote.py 98 | │ │ ├── serial.py 99 | │ │ ├── socketIot.py 100 | │ │ └── txyunIot.py 101 | │ ├── settings.py 102 | │ └── settings_user.py 103 | ├── docs/ 104 | │ ├── en/ 105 | │ │ ├── DTU_GUI_Tool_User_Guide.md 106 | │ │ ├── DTU_OTA_Upgrade_User_Guide.md 107 | │ │ ├── DTU_Solution_Development_Guide.md 108 | │ │ └── media/ 109 | │ └── zh/ 110 | │ ├── DTU GUI 工具使用说明.md 111 | │ ├── DTU OTA 升级用户指导手册.md 112 | │ ├── DTU 公版方案用户开发手册.md 113 | │ └── media/ 114 | ├── dtu_tool/ 115 | │ ├── build.sh 116 | │ ├── cloud_config.py 117 | │ ├── dtu_tool.py 118 | │ ├── LICENSE 119 | │ ├── quectel.ico 120 | │ └── translation/ 121 | │ ├── __init__.py 122 | │ ├── language 123 | │ └── zh_CN.json 124 | ├── LICENSE 125 | ├── readme.md 126 | └── readme_zh.md 127 | ``` 128 | 129 | ## Usage 130 | 131 | - [DTU Solution Development Guide](./docs/en/DTU_Solution_Development_Guide.md) 132 | - [DTU OTA Upgrade User Guide](./docs/en/DTU_OTA_Upgrade_User_Guide.md) 133 | - [DTU GUI Tool User Guide](./docs/en/DTU_GUI_Tool_User_Guide.md) 134 | 135 | ## Contributing 136 | 137 | We welcome contributions to improve this project! Please follow these steps to contribute: 138 | 139 | 1. Fork the repository. 140 | 2. Create a new branch (`git checkout -b feature/your-feature`). 141 | 3. Commit your changes (`git commit -m 'Add your feature'`). 142 | 4. Push to the branch (`git push origin feature/your-feature`). 143 | 5. Open a Pull Request. 144 | 145 | ## License 146 | 147 | This project is licensed under the Apache License. See the [LICENSE](LICENSE) file for details. 148 | 149 | ## Support 150 | 151 | If you have any questions or need support, please refer to the [QuecPython documentation](https://python.quectel.com/doc) or open an issue in this repository. 152 | -------------------------------------------------------------------------------- /docs/en/DTU_GUI_Tool_User_Guide.md: -------------------------------------------------------------------------------- 1 | # DTU GUI Tool User Guide 2 | 3 | ## 1. Overview 4 | 5 | This document mainly introduces the use of the DTU GUI tool. 6 | 7 | The DTU GUI tool is currently mainly used for customer development and debugging. It provides basic query and setting functions, as well as simulating MCU testing and DTU module data transmission and reception. Users can use a USB to TTL module to connect the PC and the DTU. 8 | 9 | The DTU GUI is developed based on wxPython. The currently compiled `dtu_gui.exe` only supports Windows systems. Users on Linux/macOS can configure the Python environment, install the wxPython library, and directly run `dtu_gui.py` or compile the corresponding version of the executable program themselves. 10 | 11 | ## 2. Running the DTU GUI Tool 12 | 13 | **Double-click to open the DTU GUI tool** 14 | 15 | ![](./media/DTU_GUI_User_Guides/dtugui.jpg) 16 | 17 | **Open the serial port** 18 | 19 | ![](./media/DTU_GUI_User_Guides/gui_open_port_done.jpg) 20 | 21 | ## 3. DTU GUI Function Introduction 22 | 23 | ## 3.1 Toolbox 24 | 25 | **The current functions of the toolbox are as follows:** 26 | | **Button Name** | **Function** | 27 | | --------------- | ------------ | 28 | | **Get Current Parameters** | Get the current configuration parameters of the DTU and jump to the `Parameter Configuration` interface to display specific parameters | 29 | | **Save All Settings and Restart** | Write the current configuration parameters in the `Parameter Configuration` interface to the DTU and restart the DTU | 30 | | **Restore Factory Settings and Restart** | Delete all configuration parameters, restore factory settings, and restart the DTU | 31 | | **Query IMEI Number** | Get the IMEI number of the DTU module | 32 | | **Query Local Number** | Get the phone number of the SIM card in the DTU | 33 | | **Query Signal Strength** | Get the CSQ signal strength, with a range of 0 ~ 31, where the larger the value, the stronger the signal strength | 34 | | **Device Restart** | Restart the DTU device | 35 | 36 | ### 3.1.1 Query IMEI Number: 37 | 38 | - Query IMEI number: 39 | 40 | ![](./media/DTU_GUI_User_Guides/gui_get_imei.jpg) 41 | 42 | The detailed serial port data is displayed in the string format in the left serial port data display box, and the obtained IMEI number is displayed in the command message box on the right. 43 | 44 | ### 3.1.2 Get Current Configuration Parameters of DTU: 45 | 46 | Click the `Get Current Parameters` button to immediately jump to the parameter configuration interface. 47 | ![](./media/DTU_GUI_User_Guides/gui_get_dtu_config.jpg) 48 | 49 | Click the interactive interface to see the specific serial port information interaction between the tool and the DTU. 50 | ![](./media/DTU_GUI_User_Guides/gui_get_dtu_config_uart_data.jpg) 51 | 52 | ## 3.3 Import Configuration Parameters 53 | After reading the current configuration parameters, enter the parameter configuration interface. You can modify the configuration according to actual needs (you can also fill in the configuration directly without reading it). 54 | ### 3.3.1 Basic Parameter Configuration 55 | 56 | ![](./media/DTU_GUI_User_Guides/gui_config_page1.jpg) 57 | The basic configuration parameters are as shown in the figure above: 58 | | **Parameter Name** | **Meaning** | 59 | | ------------------ | ----------- | 60 | | Cloud Platform Channel Type | Cloud platform selection, options: `Aliyun`, `Tencent Cloud`, `Huawei Cloud`, `Quectel Cloud`, `TCP Private Cloud`, `MQTT Private Cloud`| 61 | | Firmware Upgrade | Whether to enable firmware OTA upgrade | 62 | | Script Upgrade | Whether to enable project script OTA upgrade | 63 | | Historical Data Storage | When communication is abnormal and the DTU cannot send data to the cloud, the data to be sent is saved and resent after communication is restored | 64 | | Serial Port Number | External MCU connection DTU serial port number, options: `0`, `1`, `2` | 65 | | Baud Rate | Serial port baud rate | 66 | | Data Bits | Parity check | 67 | | Stop Bits | Stop bit length, options: `1`, `2` | 68 | | Flow Control | Hardware control flow, options: `FC_NONE`, `FC_HW` | 69 | | Control 485 Communication Direction Pin | Pull up and down the specified GPIO before and after sending data through the serial port to indicate the direction of 485 communication. For example, `1`, `2` represent `UART.GPIO1`, `UART.GPIO2`. 70 | 71 | ### 3.3.2 Cloud Parameter Configuration 72 | The cloud parameter configuration items will change according to the value selected in the basic `Cloud Platform Communication Type`. When the `Cloud Platform Communication Type` is Aliyun, the cloud parameter configuration items are as follows: 73 | 74 | ![](./media/DTU_GUI_User_Guides/gui_aliyun_config.jpg) 75 | 76 | When the `Cloud Platform Communication Type` is Quectel Cloud, the cloud parameter configuration items are as follows: 77 | ![](./media/DTU_GUI_User_Guides/gui_quecthing_config.jpg) 78 | 79 | ## 3.4 Data Sending Frame Format Requirements 80 | The data sending format is consistent with the communication format between the MCU and the DTU. Depending on the different communication protocols with the cloud, the communication protocol between the module and the external device (such as the MCU) will also be different. When the module communicates with the cloud using the TCP protocol, since both TCP and the serial port are in the form of data streams, the data is directly transmitted without any processing; when the module communicates with the cloud using the MQTT protocol, to distinguish different data frames, the module's external serial port protocol uses a simple data frame: 81 | `,,"`. 82 | **Note: Quectel Cloud does not support Topic settings, `` is uniformly `"0"`** 83 | 84 | **Example Messages:** 85 | 86 | - Uplink Message: 87 | 88 | `“1,6,abcedf”` 89 | 90 | - Downlink Message: 91 | 92 | `“1,6,ijklmn”` 93 | 94 | The uplink and downlink messages between the module and the external device (MCU) are in string format, and the data items are separated by `,`. 95 | ![](./media/DTU_GUI_User_Guides/gui_send_frame.jpg) -------------------------------------------------------------------------------- /code/modules/remote.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import _thread 16 | from usr.modules.logging import getLogger 17 | from usr.modules.common import Observable, CloudObserver 18 | from usr.modules.net_manager import NetManager 19 | 20 | 21 | log = getLogger(__name__) 22 | 23 | class RemoteSubscribe(CloudObserver): 24 | """This class is for distribute cloud downlink messages""" 25 | def __init__(self): 26 | self.__executor = None 27 | self.__ota_executor = None 28 | 29 | def __raw_data(self, *args, **kwargs): 30 | """Handle cloud transparent data transmission.""" 31 | return self.__executor.downlink_main(*args, **kwargs) if self.__executor else False 32 | 33 | def __query(self, *args, **kwargs): 34 | """Handle cloud object model quering message""" 35 | return self.__executor.event_query(*args, **kwargs) if self.__executor else False 36 | 37 | def __ota_plain(self, *args, **kwargs): 38 | """Handle cloud OTA plain""" 39 | return self.__ota_executor.event_ota_plain(*args, **kwargs) if self.__ota_executor else False 40 | 41 | def __ota_file_download(self, *args, **kwargs): 42 | """Handle cloud OTA file fragment download""" 43 | # TODO: To Download OTA File For MQTT Association (Not Support Now.) 44 | log.debug("ota_file_download: %s" % str(args)) 45 | if self.__ota_executor and hasattr(self.__ota_executor, "ota_file_download"): 46 | return self.__ota_executor.event_ota_file_download(*args, **kwargs) 47 | else: 48 | return False 49 | 50 | def __thread_execute(self, option_fun, opt_args, opt_kwargs): 51 | return option_fun(*opt_args, **opt_kwargs) 52 | 53 | def add_executor(self, executor, executor_id): 54 | """Add cloud downlink messages executor""" 55 | if executor: 56 | if executor_id == 1: 57 | self.__executor = executor 58 | return True 59 | elif executor_id == 2: 60 | self.__ota_executor = executor 61 | return True 62 | return False 63 | 64 | def execute(self, observable, *args, **kwargs): 65 | """Get cloud downlink messages from cloud. 66 | 1. observable: Cloud Iot Object. 67 | 2. args[1]: Cloud DownLink Data Type. 68 | 2.1 object_model: Set Cloud Object Model. 69 | 2.2 query: Query Cloud Object Model. 70 | 2.3 ota_plain: OTA Plain Info. 71 | 2.4 raw_data: Passthrough Data. 72 | 2.5 ota_file_download: Download OTA File For MQTT Association (Not Support Now). 73 | 3. args[2]: Cloud DownLink Data(List Or Dict). 74 | """ 75 | opt_attr = "__" + args[1] 76 | opt_args = args[2] if not isinstance(args[2], dict) else () 77 | opt_kwargs = args[2] if isinstance(args[2], dict) else {} 78 | 79 | if hasattr(self, opt_attr): 80 | option_fun = getattr(self, opt_attr) 81 | _thread.start_new_thread(self.__thread_execute, (option_fun, opt_args, opt_kwargs)) 82 | else: 83 | log.error("RemoteSubscribe Has No Attribute [%s]." % opt_attr) 84 | 85 | 86 | class RemotePublish(Observable): 87 | """This class is for post data to cloud 88 | Function: 89 | 1. Check OTA plain. 90 | 2. Confirm OTA upgrade. 91 | 3. Device & project version report. 92 | 4. RRPC response. 93 | 5. Publish object model data to cloud. 94 | """ 95 | def __init__(self): 96 | """ 97 | cloud: 98 | CloudIot Object 99 | """ 100 | super().__init__() 101 | self.__cloud = None 102 | 103 | def __cloud_conn(self, enforce=False): 104 | """Cloud connect""" 105 | return self.__cloud.init(enforce=enforce) if self.__cloud else False 106 | 107 | def __cloud_post(self, data, topic_id): 108 | """Cloud publish object model data""" 109 | try: 110 | return self.__cloud.through_post_data(data, topic_id) if self.__cloud else False 111 | except Exception as e: 112 | log.error("cloud post fault:", e) 113 | 114 | def add_cloud(self, cloud): 115 | """Add Cloud object""" 116 | if hasattr(cloud, "init") and \ 117 | hasattr(cloud, "post_data") and \ 118 | hasattr(cloud, "ota_request") and \ 119 | hasattr(cloud, "ota_action"): 120 | self.__cloud = cloud 121 | return True 122 | return False 123 | 124 | def cloud_ota_check(self): 125 | """Check ota plain""" 126 | return self.__cloud.ota_request() if self.__cloud else False 127 | 128 | def cloud_ota_action(self, action=1, module=None): 129 | """Confirm ota upgrade""" 130 | return self.__cloud.ota_action(action, module) if self.__cloud else False 131 | 132 | def cloud_device_report(self): 133 | """Device & project version report""" 134 | return self.__cloud.device_report() if self.__cloud else False 135 | 136 | def cloud_rrpc_response(self, message_id, data): 137 | """RRPC response""" 138 | return self.__cloud.rrpc_response(message_id, data) if self.__cloud else False 139 | 140 | def post_data(self, data, topic_id): 141 | """ 142 | Data format to post: 143 | 144 | { 145 | "switch": True, 146 | "energy": 100, 147 | "non_gps": [], 148 | "gps": [] 149 | } 150 | """ 151 | if not NetManager.reconnect(): 152 | log.error('Net Work not ready.') 153 | return 154 | 155 | res = True 156 | if self.__cloud_conn(): 157 | if not self.__cloud_post(data, topic_id): 158 | if self.__cloud_conn(enforce=True): 159 | if not self.__cloud_post(data, topic_id): 160 | res = False 161 | else: 162 | log.error("Cloud Connect Failed.") 163 | res = False 164 | else: 165 | log.error("Cloud Connect Failed.") 166 | res = False 167 | 168 | if res is False: 169 | # This Observer Is History 170 | self.notifyObservers(self, *[data]) 171 | 172 | return res 173 | -------------------------------------------------------------------------------- /code/modules/txyunIot.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :txyunIot.py 20 | @author :elian.wang@quectel.com 21 | @brief :DTU and Tencent cloud communication interface 22 | @version :0.1 23 | @date :2022-05-20 16:35:43 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | 28 | import uos 29 | # import log 30 | import ujson 31 | from TenCentYun import TXyun 32 | from usr.modules.logging import getLogger 33 | from usr.modules.common import CloudObservable 34 | log = getLogger(__name__) 35 | 36 | class TXYunIot(CloudObservable): 37 | """This is a class for txyun iot. 38 | 39 | This class extend CloudObservable. 40 | 41 | This class has the following functions: 42 | 1. Cloud connect and disconnect 43 | 2. Publish data to cloud 44 | 3. Subscribe data from cloud 45 | 46 | Attribute: 47 | pub_topic_dict: topic dict for publish dtu through data 48 | sub_topic_dict: topic dict for subscribe cloud through data 49 | conn_type:cloud name 50 | 51 | Run step: 52 | 1. cloud = AliYunIot(pk, ps, dk, ds, server, client_id) 53 | 2. cloud.addObserver(RemoteSubscribe) 54 | 3. cloud.init() 55 | 4. cloud.post_data(data) 56 | 5. cloud.close() 57 | """ 58 | def __init__(self, pk, ps, dk, ds, clean_session, client_id, pub_topic=None, sub_topic=None, burning_method=0, life_time=120, 59 | mcu_name="", mcu_version="", firmware_name="", firmware_version="", reconn=True): 60 | """ 61 | 1. Init parent class CloudObservable 62 | 2. Init cloud connect params and topic 63 | """ 64 | super().__init__() 65 | self.conn_type = "txyun" 66 | self.__pk = pk 67 | self.__ps = ps 68 | self.__dk = dk 69 | self.__ds = ds 70 | self.__txyun = None 71 | self.__clean_session = clean_session 72 | self.__burning_method = burning_method 73 | self.__life_time = life_time 74 | self.__mcu_name = mcu_name 75 | self.__mcu_version = mcu_version 76 | self.__firmware_name = firmware_name 77 | self.__firmware_version = firmware_version 78 | self.__reconn = reconn 79 | self.__object_model = None 80 | self.__client_id = client_id 81 | self.__post_res = {} 82 | 83 | if pub_topic == None: 84 | self.pub_topic_dict = {"0": "/%s/%s/event" % (self.__pk, self.__dk)} 85 | else: 86 | self.pub_topic_dict = pub_topic 87 | if sub_topic == None: 88 | self.sub_topic_dict = {"0": "/%s/%s/control" % (self.__pk, self.__dk)} 89 | else: 90 | self.sub_topic_dict = sub_topic 91 | 92 | def __txyun_subscribe_topic(self): 93 | if self.__txyun is None: 94 | log.error("Txyun is not connected.") 95 | return False 96 | for id, usr_sub_topic in self.sub_topic_dict.items(): 97 | if self.__txyun.subscribe(usr_sub_topic, qos=0) == -1: 98 | log.error("Topic [%s] Subscribe Falied." % usr_sub_topic) 99 | 100 | 101 | def __txyun_sub_cb(self, topic, data): 102 | """Txyun subscribe topic callback 103 | 104 | Parameter: 105 | topic: topic info 106 | data: response dictionary info 107 | """ 108 | topic = topic.decode() 109 | try: 110 | data = ujson.loads(data) 111 | except: 112 | pass 113 | 114 | try: 115 | self.notifyObservers(self, *("raw_data", {"topic":topic, "data":data} ) ) 116 | except Exception as e: 117 | log.error("{}".format(e)) 118 | 119 | def init(self, enforce=False): 120 | """Txyun connect and subscribe topic 121 | 122 | Parameter: 123 | enforce: 124 | True: enfore cloud connect and subscribe topic 125 | False: check connect status, return True if cloud connected 126 | 127 | Return: 128 | Ture: Success 129 | False: Failed 130 | """ 131 | log.debug("[init start] enforce: %s" % enforce) 132 | if enforce is False and self.__txyun is not None: 133 | log.debug("self.get_status(): %s" % self.get_status()) 134 | if self.get_status(): 135 | return True 136 | 137 | if self.__txyun is not None: 138 | self.close() 139 | 140 | if self.__burning_method == 0: 141 | self.__ds = None 142 | elif self.__burning_method == 1: 143 | self.__ps = None 144 | 145 | log.debug("TxYun init. self.__pk: %s, self.__ps: %s, self.__dk: %s, self.__ds: %s" % (self.__pk, self.__ps, self.__dk, self.__ds)) 146 | self.__txyun = TXyun(self.__pk, self.__dk, self.__ds, self.__ps) 147 | log.debug("TxYun setMqtt.") 148 | setMqttres = self.__txyun.setMqtt(clean_session=self.__clean_session, keepAlive=self.__life_time, reconn=self.__reconn) 149 | log.debug("TxYun setMqttres: %s" % setMqttres) 150 | if setMqttres != -1: 151 | setCallbackres = self.__txyun.setCallback(self.__txyun_sub_cb) 152 | log.debug("TxYun setCallback: %s" % setCallbackres) 153 | self.__txyun_subscribe_topic() 154 | log.debug("TxYun __txyun_subscribe_topic") 155 | self.__txyun.start() 156 | log.debug("TxYun start.") 157 | else: 158 | log.error("setMqtt Falied!") 159 | del self.__txyun 160 | self.__txyun = None 161 | return False 162 | 163 | log.debug("self.get_status(): %s" % self.get_status()) 164 | if self.get_status(): 165 | return True 166 | else: 167 | return False 168 | 169 | def close(self): 170 | """TxYun disconnect""" 171 | if self.__txyun is None: 172 | return False 173 | try: 174 | self.__txyun.disconnect() 175 | except: 176 | pass 177 | return True 178 | 179 | def get_status(self): 180 | """Get TxYun connect status 181 | 182 | Return: 183 | True -- connect success 184 | False -- connect falied 185 | """ 186 | if self.__txyun is None: 187 | return False 188 | try: 189 | return True if self.__txyun.getTXyunsta() == 0 else False 190 | except: 191 | return False 192 | 193 | def through_post_data(self, data, topic_id): 194 | """Publish through data 195 | Return: 196 | Ture: Success 197 | False: Failed 198 | """ 199 | if self.__txyun is None: 200 | log.error("Txyun is not connected.") 201 | return False 202 | try: 203 | pub_res = self.__txyun.publish(self.pub_topic_dict[topic_id], data, qos=0) 204 | return pub_res 205 | 206 | except Exception: 207 | log.error("Txyun publish topic %s failed. data: %s" % (self.pub_topic_dict[topic_id], data)) 208 | return False 209 | 210 | def post_data(self, data): 211 | return False 212 | 213 | def ota_request(self): 214 | return False 215 | 216 | def ota_action(self, action, module=None): 217 | return False 218 | 219 | def device_report(self): 220 | return False -------------------------------------------------------------------------------- /code/modules/mqttIot.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :mqttIot.py 20 | @author :elian.wang@quectel.com 21 | @brief :universal mqtt iot inferface 22 | @version :0.1 23 | @date :2022-05-18 13:28:53 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | import ujson 28 | import utime 29 | import _thread 30 | from umqtt import MQTTClient 31 | from usr.modules.logging import getLogger 32 | from usr.modules.common import CloudObservable 33 | log = getLogger(__name__) 34 | 35 | class MqttIot(CloudObservable): 36 | """This is a class for universal mqtt iot. 37 | 38 | This class extend CloudObservable. 39 | 40 | This class has the following functions: 41 | 1. Cloud connect and disconnect 42 | 2. Publish data to cloud 43 | 3. Subscribe data from cloud 44 | 45 | Attribute: 46 | pub_topic_dict: topic dict for publish dtu through data 47 | sub_topic_dict: topic dict for subscribe cloud through data 48 | conn_type:cloud name 49 | 50 | Run step: 51 | 1. cloud = MqttIot(server, qos, port, clean_session, client_id, pub_topic, sub_topic) 52 | 2. cloud.addObserver(RemoteSubscribe) 53 | 3. cloud.init() 54 | 4. cloud.post_data(data) 55 | 5. cloud.close() 56 | """ 57 | def __init__(self, server, qos, port, clean_session, client_id, user, pass_word, pub_topic=None, sub_topic=None, life_time=120): 58 | """ 59 | 1. Init parent class CloudObservable 60 | 2. Init cloud connect params and topic 61 | """ 62 | super().__init__() 63 | self.conn_type = "mqtt" 64 | self.__pk = None 65 | self.__ps = None 66 | self.__dk = user 67 | self.__ds = None 68 | self.__server = server 69 | self.__qos = qos 70 | self.__port = port 71 | self.__mqtt = None 72 | self.__clean_session = clean_session 73 | self.__life_time = life_time 74 | self.__client_id = client_id 75 | self.__password = pass_word 76 | 77 | if pub_topic == None: 78 | self.pub_topic_dict = {"0": "/python/mqtt/pub"} 79 | else: 80 | self.pub_topic_dict = pub_topic 81 | if sub_topic == None: 82 | self.sub_topic_dict = {"0": "/python/mqtt/sub"} 83 | else: 84 | self.sub_topic_dict = sub_topic 85 | 86 | def __subscribe_topic(self): 87 | """Subscribe to all configured topics 88 | 89 | Returns: 90 | bool: True if all subscriptions succeeded, False if any failed 91 | """ 92 | if self.__mqtt is None: 93 | log.error("MQTT client is not initialized") 94 | return False 95 | 96 | success = True 97 | for id, usr_sub_topic in self.sub_topic_dict.items(): 98 | try: 99 | result = self.__mqtt.subscribe(usr_sub_topic, qos=0) 100 | if result == -1: 101 | log.error("Topic [%s] Subscribe Failed." % usr_sub_topic) 102 | success = False 103 | else: 104 | log.debug("Successfully subscribed to topic: %s" % usr_sub_topic) 105 | except Exception as e: 106 | log.error("Error subscribing to topic [%s]: %s" % (usr_sub_topic, str(e))) 107 | success = False 108 | return success 109 | 110 | def __sub_cb(self, topic, data): 111 | """mqtt subscribe topic callback 112 | 113 | Parameter: 114 | topic: topic info 115 | data: response dictionary info 116 | """ 117 | topic = topic.decode() 118 | log.debug("__sub_cb topic: {} data {}".format(topic, data)) 119 | try: 120 | self.notifyObservers(self, *("raw_data", {"topic":topic, "data":data} ) ) 121 | except Exception as e: 122 | log.error("{}".format(e)) 123 | 124 | def __listen(self): 125 | if self.__mqtt is not None: 126 | while True: 127 | self.__mqtt.wait_msg() 128 | log.debug("listening...") 129 | log.debug("MQTT conn status: {}".format(self.__mqtt.get_mqttsta())) 130 | utime.sleep(0.1) 131 | else: 132 | log.error("Trying to listen when MQTT is uninitialized") 133 | 134 | def __start_listen(self): 135 | """Start a new thread to listen to the cloud publish 136 | """ 137 | _thread.start_new_thread(self.__listen, ()) 138 | 139 | def init(self, enforce=False) -> bool: 140 | """mqtt connect and subscribe topic 141 | 142 | Parameter: 143 | enforce: 144 | True: enforce cloud connect and subscribe topic 145 | False: check connect status, return True if cloud connected 146 | 147 | Return: 148 | True: Success 149 | False: Failed 150 | """ 151 | log.debug("[init start] enforce: %s" % enforce) 152 | if enforce is False and self.__mqtt is not None: 153 | log.debug("self.get_status(): %s" % self.get_status()) 154 | if self.get_status(): 155 | return True 156 | 157 | if self.__mqtt is not None: 158 | self.close() 159 | 160 | log.debug("mqtt init. self.__client_id: %s, self.__password: %s, self.__dk: %s, self.__ds: %s" % (self.__client_id, self.__password, self.__dk, self.__ds)) 161 | self.__mqtt = MQTTClient(client_id=self.__client_id, server=self.__server, port=self.__port, 162 | user=self.__dk, password=self.__password, keepalive=self.__life_time, ssl=False) 163 | try: 164 | self.__mqtt.connect(clean_session=self.__clean_session) 165 | except Exception as e: 166 | log.error("mqtt connect error: %s" % e) 167 | self.__mqtt.set_callback(self.__sub_cb) 168 | 169 | if not self.__subscribe_topic(): 170 | log.error("Failed to subscribe to one or more topics") 171 | self.close() 172 | return False 173 | 174 | self.__start_listen() 175 | log.debug("mqtt start.") 176 | 177 | log.debug("self.get_status(): %s" % self.get_status()) 178 | if self.get_status(): 179 | return True 180 | else: 181 | return False 182 | 183 | def close(self): 184 | if self.__mqtt is None: 185 | return False 186 | self.__mqtt.disconnect() 187 | return True 188 | 189 | def get_status(self): 190 | """Get mqtt connect status 191 | 192 | Return: 193 | True -- connect success 194 | False -- connect falied 195 | """ 196 | if self.__mqtt is None: 197 | return False 198 | try: 199 | return True if self.__mqtt.get_mqttsta() == 0 else False 200 | except: 201 | return False 202 | 203 | def through_post_data(self, data, topic_id): 204 | if self.__mqtt is None: 205 | log.error("mqtt is not connected.") 206 | return False 207 | try: 208 | self.__mqtt.publish(self.pub_topic_dict[topic_id], data, self.__qos) 209 | except Exception: 210 | log.error("mqtt publish topic %s failed. data: %s" % (self.pub_topic_dict[topic_id], data)) 211 | return False 212 | else: 213 | return True 214 | 215 | def post_data(self, data): 216 | return False 217 | 218 | def ota_request(self): 219 | return False 220 | 221 | def ota_action(self, action, module=None): 222 | return False 223 | 224 | def device_report(self): 225 | return False 226 | -------------------------------------------------------------------------------- /code/modules/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import _thread 16 | 17 | LOWENERGYMAP = { 18 | "EC200U": [ 19 | "POWERDOWN", 20 | "PM", 21 | ], 22 | "EC200U": [ 23 | "POWERDOWN", 24 | "PM", 25 | ], 26 | "EC600N": [ 27 | "PM", 28 | ], 29 | "EC800G": [ 30 | "PM" 31 | ], 32 | } 33 | 34 | 35 | def numiter(num=99999): 36 | """Number generation iterator""" 37 | for i in range(num): 38 | yield i 39 | 40 | 41 | def option_lock(thread_lock): 42 | """Function thread lock decorator""" 43 | def function_lock(func): 44 | def wrapperd_fun(*args, **kwargs): 45 | with thread_lock: 46 | return func(*args, **kwargs) 47 | return wrapperd_fun 48 | return function_lock 49 | 50 | 51 | class BaseError(Exception): 52 | """Exception base class""" 53 | 54 | def __init__(self, value): 55 | self.value = value 56 | 57 | def __str__(self): 58 | return repr(self.value) 59 | 60 | 61 | class Singleton(object): 62 | """Singleton base class""" 63 | _instance_lock = _thread.allocate_lock() 64 | 65 | def __init__(self, *args, **kwargs): 66 | pass 67 | 68 | def __new__(cls, *args, **kwargs): 69 | if not hasattr(cls, "instance_dict"): 70 | Singleton.instance_dict = {} 71 | 72 | if str(cls) not in Singleton.instance_dict.keys(): 73 | with Singleton._instance_lock: 74 | _instance = super().__new__(cls) 75 | Singleton.instance_dict[str(cls)] = _instance 76 | 77 | return Singleton.instance_dict[str(cls)] 78 | 79 | 80 | class Observer(object): 81 | """Observer base class""" 82 | 83 | def update(self, observable, *args, **kwargs): 84 | pass 85 | 86 | 87 | class Observable(Singleton): 88 | """Observable base class""" 89 | 90 | def __init__(self): 91 | self.__observers = [] 92 | 93 | def addObserver(self, observer): 94 | """Add observer""" 95 | try: 96 | self.__observers.append(observer) 97 | return True 98 | except: 99 | return False 100 | 101 | def delObserver(self, observer): 102 | """Delete observer""" 103 | try: 104 | self.__observers.remove(observer) 105 | return True 106 | except: 107 | return False 108 | 109 | def notifyObservers(self, *args, **kwargs): 110 | """Notify observer""" 111 | for o in self.__observers: 112 | o.update(self, *args, **kwargs) 113 | 114 | 115 | class CloudObserver(object): 116 | """Cloud observer base class""" 117 | 118 | def execute(self, observable, *args, **kwargs): 119 | pass 120 | 121 | 122 | class CloudObservable(Singleton): 123 | """Cloud observable base class""" 124 | 125 | def __init__(self): 126 | self.__observers = [] 127 | 128 | def addObserver(self, observer): 129 | """Add observer""" 130 | self.__observers.append(observer) 131 | 132 | def delObserver(self, observer): 133 | """Delete observer""" 134 | self.__observers.remove(observer) 135 | 136 | def notifyObservers(self, *args, **kwargs): 137 | """Notify observer""" 138 | for o in self.__observers: 139 | o.execute(self, *args, **kwargs) 140 | 141 | def init(self, enforce=False) -> bool: 142 | """Cloud init""" 143 | return False 144 | 145 | def close(self) -> bool: 146 | """Cloud disconnect""" 147 | return False 148 | 149 | def post_data(self, data) -> bool: 150 | """Cloud publish data""" 151 | return False 152 | 153 | def ota_request(self, *args, **kwargs) -> bool: 154 | """Cloud publish ota plain request""" 155 | return False 156 | 157 | def ota_action(self, action, module=None) -> bool: 158 | """Cloud publish ota upgrade or not request""" 159 | return False 160 | 161 | 162 | class CloudObjectModel(Singleton): 163 | """This is a cloud object model base class 164 | 165 | Attribute: 166 | items: object model dictionary, default two keys 167 | events: object model events 168 | property: object model property 169 | 170 | items data format: 171 | { 172 | "events": { 173 | "name": "events", 174 | "id": "", 175 | "perm": "rw", 176 | "struct_info": { 177 | "name": "struct", 178 | "id": "", 179 | "struct_info": { 180 | "key": { 181 | "name": "key" 182 | } 183 | }, 184 | }, 185 | }, 186 | "property": { 187 | "name": "event", 188 | "id": "", 189 | "perm": "rw", 190 | "struct_info": {} 191 | } 192 | } 193 | """ 194 | 195 | def __init__(self, om_file): 196 | self.items = { 197 | "events": {}, 198 | "properties": {}, 199 | } 200 | self.om_file = om_file 201 | 202 | def init(self): 203 | pass 204 | 205 | def set_item(self, om_type, om_key, om_key_id=None, om_key_perm=None): 206 | """ Set object model item 207 | 208 | Parameter: 209 | om_type: object model type 210 | - e.g.: `events`, `properties` 211 | 212 | om_key: object model code 213 | - e.g.: `local_time`, `speed`, `GeoLocation` 214 | 215 | om_key_id: object model id, not necessary, necessary for quecthing. 216 | 217 | om_key_perm: object model permission, not necessary 218 | - e.g.: `rw`, `w`, `r` 219 | 220 | Return: 221 | True: Success 222 | False: Failed 223 | """ 224 | om_data = { 225 | "name": om_key, 226 | "id": om_key_id, 227 | "perm": om_key_perm, 228 | "struct_info": {} 229 | } 230 | if self.items.get(om_type) is not None: 231 | self.items[om_type][om_key] = om_data 232 | return True 233 | return False 234 | 235 | def del_item(self, om_type, om_key): 236 | """Delete object model item 237 | 238 | Parameter: 239 | om_type: object model type 240 | om_key: object model code 241 | 242 | Return: 243 | True: Success 244 | False: Failed 245 | """ 246 | if self.items.get(om_type) is not None: 247 | if self.items[om_type].get(om_key) is not None: 248 | self.items[om_type].pop(om_key) 249 | return True 250 | return False 251 | 252 | def set_item_struct(self, om_type, om_key, struct_key, struct_key_id=None, struct_key_struct=None): 253 | """Set object model item struct 254 | 255 | Parameter: 256 | om_type: object model type 257 | om_key: object model code 258 | struct_key: object model item struct key name 259 | struct_key_id: object model item struct key id, not necessary 260 | struct_key_struct: object model item struct key struct, not necessary 261 | 262 | Return: 263 | True: Success 264 | False: Failed 265 | """ 266 | if self.items.get(om_type) is not None: 267 | if self.items[om_type].get(om_key) is not None: 268 | if self.items[om_type][om_key].get("struct_info") is None: 269 | self.items[om_type][om_key]["struct_info"] = {} 270 | self.items[om_type][om_key]["struct_info"][struct_key] = { 271 | "name": struct_key, 272 | "id": struct_key_id, 273 | "struct_info": struct_key_struct, 274 | } 275 | return True 276 | return False 277 | -------------------------------------------------------------------------------- /code/modules/huawei_cloud.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :huawei_cloud.py 20 | @author :elian.wang@quectel.com 21 | @brief :This file shows the interface of Huawei cloud 22 | @version :0.1 23 | @date :2022-05-18 09:14:22 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | 28 | import ujson 29 | import utime 30 | import _thread 31 | import ubinascii 32 | import uhashlib 33 | 34 | 35 | from umqtt import MQTTClient 36 | from usr.modules.logging import getLogger 37 | from usr.modules.common import CloudObservable 38 | log = getLogger(__name__) 39 | 40 | 41 | class HuaweiIot(CloudObservable): 42 | """This is a class for huaweiyun iot. 43 | 44 | This class extend CloudObservable. 45 | 46 | This class has the following functions: 47 | 1. Cloud connect and disconnect 48 | 2. Publish data to cloud 49 | 3. Subscribe data from cloud 50 | 51 | Attribute: 52 | pub_topic_dict: topic dict for publish dtu through data 53 | sub_topic_dict: topic dict for subscribe cloud through data 54 | conn_type:cloud name 55 | 56 | Run step: 57 | 1. cloud = HuaweiIot(pk, ps, dk, ds, server, qos, port, clean_session, client_id, pub_topic, sub_topic) 58 | 2. cloud.addObserver(RemoteSubscribe) 59 | 3. cloud.init() 60 | 4. cloud.post_data(data) 61 | 5. cloud.close() 62 | """ 63 | def __init__(self, pk, ps, dk, ds, server, qos, port, clean_session, client_id, pub_topic=None, sub_topic=None, life_time=120, 64 | mcu_name="", mcu_version="", firmware_name="", firmware_version="", reconn=True): 65 | """ 66 | 1. Init parent class CloudObservable 67 | 2. Init cloud connect params and topic 68 | """ 69 | super().__init__() 70 | self.conn_type = "hwyun" 71 | self.__pk = pk 72 | self.__ps = ps 73 | self.__dk = dk 74 | self.__ds = ds 75 | self.__server = server 76 | self.__qos = qos 77 | self.__port = port 78 | self.__huaweiyun = None 79 | self.__clean_session = clean_session 80 | self.__life_time = life_time 81 | self.__mcu_name = mcu_name 82 | self.__mcu_version = mcu_version 83 | self.__firmware_name = firmware_name 84 | self.__firmware_version = firmware_version 85 | self.__reconn = reconn 86 | self.__object_model = None 87 | self.__client_id = client_id 88 | self.__post_res = {} 89 | self.__password = None 90 | 91 | if pub_topic == None: 92 | self.pub_topic_dict = {"0": "$oc/devices/%s/sys/messages/up" % (self.__dk)} 93 | else: 94 | self.pub_topic_dict = pub_topic 95 | if sub_topic == None: 96 | self.sub_topic_dict = {"0": "$oc/devices/%s/sys/messages/down" % (self.__dk)} 97 | else: 98 | self.sub_topic_dict = sub_topic 99 | 100 | def __huaweiyun_subscribe_topic(self): 101 | if self.__huaweiyun is None: 102 | log.error("HuaweiYun MQTTClient is None, please init first.") 103 | return 104 | for id, usr_sub_topic in self.sub_topic_dict.items(): 105 | if self.__huaweiyun.subscribe(usr_sub_topic, qos=0) == -1: 106 | log.error("Topic [%s] Subscribe Falied." % usr_sub_topic) 107 | 108 | 109 | def __huaweiyun_sub_cb(self, topic, data): 110 | """Huaweiyun subscribe topic callback 111 | 112 | Parameter: 113 | topic: topic info 114 | data: response dictionary info 115 | """ 116 | topic = topic.decode() 117 | try: 118 | data = ujson.loads(data) 119 | data = data["content"] 120 | except: 121 | pass 122 | 123 | try: 124 | self.notifyObservers(self, *("raw_data", {"topic":topic, "data":data} ) ) 125 | except Exception as e: 126 | log.error("{}".format(e)) 127 | 128 | def __listen(self): 129 | if self.__huaweiyun is None: 130 | log.error("HuaweiYun MQTTClient is None, please init first.") 131 | return 132 | while True: 133 | self.__huaweiyun.wait_msg() 134 | utime.sleep_ms(100) 135 | 136 | def __start_listen(self): 137 | """Start a new thread to listen to the cloud publish 138 | """ 139 | _thread.start_new_thread(self.__listen, ()) 140 | 141 | @staticmethod 142 | def __hmac_sha256_digest(key_K, data): 143 | """huwei iot generate password 144 | 145 | Args: 146 | key_K (_type_): time sign 147 | data (_type_): device secret 148 | """ 149 | def xor(x, y): 150 | return bytes(x[i] ^ y[i] for i in range(min(len(x), len(y)))) 151 | 152 | if len(key_K) > 64: 153 | raise ValueError("The key must be <= 64 bytes in length") 154 | padded_K = key_K + b"\x00" * (64 - len(key_K)) 155 | ipad = b"\x36" * 64 156 | opad = b"\x5c" * 64 157 | h_inner = uhashlib.sha256(xor(padded_K, ipad)) 158 | h_inner.update(data) 159 | h_outer = uhashlib.sha256(xor(padded_K, opad)) 160 | h_outer.update(h_inner.digest()) 161 | return ubinascii.hexlify(h_outer.digest()).decode() 162 | 163 | def init(self, enforce=False) -> bool: 164 | """Huweiyun connect and subscribe topic 165 | 166 | Parameter: 167 | enforce: 168 | True: enfore cloud connect and subscribe topic 169 | False: check connect status, return True if cloud connected 170 | 171 | Return: 172 | Ture: Success 173 | False: Failed 174 | """ 175 | log.debug("[init start] enforce: %s" % enforce) 176 | if enforce is False and self.__huaweiyun is not None: 177 | log.debug("self.get_status(): %s" % self.get_status()) 178 | if self.get_status(): 179 | return True 180 | 181 | if self.__huaweiyun is not None: 182 | self.close() 183 | 184 | local_time = utime.localtime() 185 | time_sign = "%s%s%s%s" % (local_time[0], "%02d" % local_time[1], "%02d" % local_time[2], "%02d" % local_time[3]) 186 | self.__client_id = self.__dk + "_0_0_" + time_sign 187 | self.__password = self.__hmac_sha256_digest(time_sign.encode("utf-8"), self.__ds.encode("utf-8")) 188 | 189 | log.debug("HuaweiYun init. self.__client_id: %s, self.__password: %s, self.__dk: %s, self.__ds: %s" % (self.__client_id, self.__password, self.__dk, self.__ds)) 190 | self.__huaweiyun = MQTTClient(client_id=self.__client_id, server=self.__server, port=self.__port, 191 | user=self.__dk, password=self.__password, keepalive=self.__life_time, ssl=False) 192 | try: 193 | self.__huaweiyun.connect(clean_session=self.__clean_session) 194 | except Exception as e: 195 | log.error("HuaweiYun connect error: %s" % e) 196 | else: 197 | self.__huaweiyun.set_callback(self.__huaweiyun_sub_cb) 198 | self.__huaweiyun_subscribe_topic() 199 | log.debug("HuaweiYun __huaweiyun_subscribe_topic") 200 | self.__start_listen() 201 | log.debug("HuaweiYun start.") 202 | 203 | log.debug("self.get_status(): %s" % self.get_status()) 204 | if self.get_status(): 205 | return True 206 | else: 207 | return False 208 | 209 | def close(self): 210 | if self.__huaweiyun is None: 211 | return False 212 | self.__huaweiyun.disconnect() 213 | return True 214 | 215 | def get_status(self): 216 | """Get huaweiyun connect status 217 | 218 | Return: 219 | True -- connect success 220 | False -- connect falied 221 | """ 222 | if self.__huaweiyun is None: 223 | return False 224 | try: 225 | return True if self.__huaweiyun.get_mqttsta() == 0 else False 226 | except: 227 | return False 228 | 229 | def through_post_data(self, data, topic_id): 230 | if self.__huaweiyun is None: 231 | log.error("Huaweiyun is not connected.") 232 | return False 233 | try: 234 | self.__huaweiyun.publish(self.pub_topic_dict[topic_id], data, self.__qos) 235 | except Exception: 236 | log.error("Huaweiyun publish topic %s failed. data: %s" % (self.pub_topic_dict[topic_id], data)) 237 | return False 238 | else: 239 | return True 240 | 241 | def post_data(self, data): 242 | return False 243 | 244 | def ota_request(self): 245 | return False 246 | 247 | def ota_action(self, action, module=None): 248 | return False 249 | 250 | def device_report(self): 251 | return False -------------------------------------------------------------------------------- /docs/zh/DTU 公版方案用户开发手册.md: -------------------------------------------------------------------------------- 1 | 2 | # DTU 公版方案用户开发手册 3 | 4 | ## 1. 基本概述 5 | 6 | 本文档主要基于介绍DTU项目的文件目录及使用接口说明,方便客户快速熟悉DTU方案。 7 | 8 | ## 2. 项目目录 9 | ``` 10 | |--code 11 | |--dtu_config.json 12 | |--dtu.py 13 | |--dtu_transaction.py 14 | |--settings.py 15 | |--settings_user.py 16 | |--modules 17 | |--aliyunIot.py 18 | |--common.py 19 | |--history.py 20 | |--huawei_cloud.py 21 | |--logging.py 22 | |--mqttIot.py 23 | |--quecthing.py 24 | |--remote.py 25 | |--socketIot.py 26 | |--txyunIot.py 27 | |--serial.py 28 | ``` 29 | 30 | | 文件名 | 说明 | 31 | |---------- |----------------------------------------- | 32 | |`dtu_config.json` | 配置文件,用户配置参数| 33 | |`dtu.py`| dtu主文件,包含dtu的所有初始化逻辑| 34 | |`dtu_transaction.py`| DTU业务逻辑文件,关于DTU的所有业务逻辑都在此文件中| 35 | |`settings.py`| 配置参数读写模块| 36 | |`settings_user.py`| 配置参数默认值,DTU初始化时未找到dtu_config.json文件时,使用此文件中默认配置| 37 | |`modules.aliyunIot.py`| 阿里云模块,主要用于与云端的消息交互与OTA升级| 38 | |`modules.common.py`| 通用接口库| 39 | |`modules.history.py`| 历史文件读写操作模块| 40 | |`modules.huawei_cloud.py`| 华为云模块,主要用于与云端的消息交互| 41 | |`modules.logging.py`| QuecPython log打印接口| 42 | |`modules.mqttIot.py`| MQTT私有云接口,和云端连接通信| 43 | |`modules.quecthing.py`| 移远云模块,主要用于与云端的消息交互与OTA升级| 44 | |`modules.remote.py`| 云端和业务逻辑中间件,将业务和云端接口隔离| 45 | |`modules.socketIot.py`| TCP协议通信模块,主要用于与云端的消息交互| 46 | |`modules.txyunIot.py`| 腾讯云模块,主要用于与云端的消息交互| 47 | |`modules.serial.py`| 串口收发接口| 48 | *注:modules文件夹下为通用接口文件,一般不需要修改* 49 | 50 | ## 3. 业务API功能说明 51 | 52 | ### 3.1. `dtu.py` 53 | dtu主文件,包含dtu的所有初始化逻辑。当dtu.py执行时调用 54 | ```ptyhon 55 | if __name__ == "__main__": 56 | dtu = Dtu() 57 | dtu.start() 58 | ``` 59 | Dtu类完成初始化,开始DTU业务执行。 60 | #### 3.1.1. Dtu()类 61 | 包含所有DTU主要模块的初始化。 62 | **cloud_init** 63 | 64 | > 业务功能: 65 | > 66 | > 完成云初始化,并连接云端服务器 67 | 68 | 例: 69 | 70 | ```python 71 | cloud = self.cloud_init(settings.current_settings["system_config"]["cloud"]) 72 | ``` 73 | 74 | 参数: 75 | 76 | | 参数 | 类型 | 说明 | 77 | |---|---|---| 78 | | protocol | str | 云类型名,如`aliyun`| 79 | 80 | 返回值: 81 | 82 | |数据类型|说明| 83 | |:---|---| 84 | |object|云对象| 85 | 86 | **start** 87 | 88 | > 业务功能: 89 | > 90 | > 完成DTU所有模块的初始化,开启DTU业务 91 | 92 | 例: 93 | 94 | ```python 95 | cloud = self.cloud_init(settings.current_settings["system_config"]["cloud"]) 96 | ``` 97 | 98 | 参数: 99 | 100 | 无 101 | 102 | 返回值: 103 | 104 | 无 105 | 106 | ### 3.2. `dtu_transaction.py` 107 | 108 | #### 3.2.1. `DownlinkTransaction()`类 109 | DTU数据下行业务,读取云端信息下发给串口。做为执行器注册给remoto pub模块,当云端发送数据时,调用DownlinkTransaction模块去解析。 110 | 111 | **add_module** 112 | 113 | > 可注册模块: 114 | > 115 | > `Serial` 116 | > 117 | > 业务功能: 118 | > 119 | > 将串口模块以注册的方式添加进上行数据业务模块 120 | 121 | 例: 122 | 123 | ```python 124 | # DownlinkTransaction initialization 125 | down_transaction = DownlinkTransaction() 126 | down_transaction.add_module(serial) 127 | ``` 128 | 129 | 参数: 130 | 131 | | 参数 | 类型 | 说明 | 132 | |---|---|---| 133 | | module | object | 模块对象 | 134 | 135 | 返回值: 136 | 137 | |数据类型|说明| 138 | |:---|---| 139 | |BOOL|`True`成功, `False`失败| 140 | 141 | 142 | **__get_sub_topic_id** 143 | 144 | > 业务功能: 145 | > 146 | > 从云参数配置中订阅Topic配置中找到Topic对应的Topic id 147 | 148 | 例: 149 | 150 | ```python 151 | # Get mqtt protocol message id 152 | cloud_type = settings.current_settings["system_config"]["cloud"] 153 | if cloud_type in ["aliyun", "txyun", "hwyun", "mqtt_private_cloud"]: 154 | msg_id = self.__get_sub_topic_id(kwargs.get("topic")) 155 | if msg_id == None: 156 | raise Exception("Not found correct topic id") 157 | ``` 158 | 159 | 参数: 160 | 161 | | 参数 | 类型 | 说明 | 162 | |---|---|---| 163 | | topic | str | 从云端收到的订阅的Topic值 | 164 | 165 | 返回值: 166 | 167 | |数据类型|说明| 168 | |:---|---| 169 | |str|Topic id| 170 | 171 | 172 | **downlink_main** 173 | 174 | > 业务功能: 175 | > 176 | > 数据下行业务主函数,接收云端数据从串口发出 177 | 178 | 例: 179 | 180 | ```python 181 | def __raw_data(self, *args, **kwargs): 182 | """Handle cloud transparent data transmission.""" 183 | return self.__executor.downlink_main(*args, **kwargs) if self.__executor else False 184 | ``` 185 | 186 | 参数: 187 | 188 | | 参数 | 类型 | 说明 | 189 | |---|---|---| 190 | | topic | str | 从云端收到的订阅的Topic值 | 191 | 192 | 返回值: 193 | 194 | |数据类型|说明| 195 | |:---|---| 196 | |str|Topic id| 197 | 198 | #### 3.2.2. `OtaTransaction()`类 199 | 执行OTA业务,配合云模块的OTA接口对模块进行升级。 200 | 具体业务逻辑如下: 201 | 1.模块上电将模块名称及版本号上传云端,云端记录此信息。 202 | 2.当模块的名称和版本号存在升级计划,则云端会向模块发送升级计划信息。 203 | 3.模块对升级计划中的版本信息再次检查,确定要升级的版本号与当前模块的版本号不同,则开始下载固件。 204 | 4.固件下载完成开始更新固件,固件更新完成模块进行重启(**此过程需要等待1~2分钟,严禁重启或断电**) 205 | 206 | **ota_check** 207 | 208 | > 业务功能: 209 | > 210 | > DTU初始化时调用,向云端发送模块名称和版本号 211 | 212 | 例: 213 | 214 | ```python 215 | # Send module release information to cloud. After receiving this information, 216 | # the cloud server checks whether to upgrade modules 217 | ota_transaction.ota_check() 218 | ``` 219 | 220 | 参数: 221 | 222 | 无 223 | 224 | 返回值: 225 | 226 | 无 227 | 228 | **event_ota_plain** 229 | 230 | > 业务功能: 231 | > 232 | > - 该模块为`RemoteSubscribe`OTA升级计划信息监听功能 233 | > - 收到被监听者的消息通知时,进行处理 234 | > - 检测设备是否开启OTA升级,否则取消OTA升级 235 | > - 调用`Controller.remote_ota_action`功能进行OTA升级 236 | 237 | 例: 238 | 239 | ```python 240 | def __ota_plain(self, *args, **kwargs): 241 | """Handle cloud OTA plain""" 242 | return self.__ota_executor.event_ota_plain(*args, **kwargs) if self.__ota_executor else False 243 | ``` 244 | 245 | 参数: 246 | 247 | 无 248 | 249 | 返回值: 250 | 251 | 无 252 | 253 | #### 3.2.3. `UplinkTransaction()`类 254 | DTU数据上行业务,读取串口信息上传给云端。 255 | 256 | **__get_pub_topic_id_list** 257 | 258 | > 业务功能: 259 | > 260 | > - 从云配置中获取发布Topic id列表 261 | 262 | 例: 263 | 264 | ```python 265 | pub_topic_id_list = self.__get_pub_topic_id_list() 266 | ``` 267 | 268 | 参数: 269 | 270 | 无 271 | 272 | 返回值: 273 | 274 | |数据类型|说明| 275 | |:---|---| 276 | |list|发布Topic id列表| 277 | 278 | **__parse** 279 | 280 | > 业务功能: 281 | > 282 | > - 当和云端通信采用MQTT协议时,解析串口数据(和云端通信协议不同时,模块的串口协议也不同) 283 | > - 本函数可递归,当串口数据流中包含不止一帧完整数据时,会递归继续调用解析。 284 | 285 | 例: 286 | 287 | ```python 288 | self.__parse_data += data 289 | self.__send_to_cloud_data = [] 290 | self.__parse() 291 | ``` 292 | 293 | 参数: 294 | 295 | 无 296 | 297 | 返回值: 298 | 299 | 无 300 | 301 | **__uplink_data** 302 | 303 | > 业务功能: 304 | > 305 | > - 将从串口读取的数据解析并发送 306 | > - 当和云端通信采用MQTT协议时,发送数据给云端时会创建新线程执行发送过程(串口数据可能包含多帧MQTT协议,时间花费比较长,可能会造成串口数据读取延迟,故单独创建线程执行)。 307 | 308 | 例: 309 | 310 | ```python 311 | def uplink_main(self): 312 | """Read serial data, parse and upload to the cloud 313 | """ 314 | while 1: 315 | # Read uart data 316 | read_byte = self.__serial.read(nbytes=1024, timeout=100) 317 | if read_byte: 318 | try: 319 | self.__uplink_data(read_byte) 320 | except Exception as e: 321 | usys.print_exception(e) 322 | log.error("Parse uart data error: %s" % e) 323 | ``` 324 | 325 | 参数: 326 | 327 | | 参数 | 类型 | 说明 | 328 | |---|---|---| 329 | | topic | str | 从云端收到的订阅的Topic值 | 330 | 331 | 返回值: 332 | 333 | 无 334 | 335 | **__post_history_data** 336 | 337 | > 业务功能: 338 | > 339 | > - 将历史数据发送给云端 340 | 341 | 例: 342 | 343 | ```python 344 | if hist["data"]: 345 | pt_count = 0 346 | for i, data in enumerate(hist["data"]): 347 | pt_count += 1 348 | if not self.__post_history_data(data): 349 | res = False 350 | break 351 | 352 | hist["data"] = hist["data"][pt_count:] 353 | if hist["data"]: 354 | # Flush data in hist-dictionary to tracker_data.hist file. 355 | self.__history.write(hist["data"]) 356 | ``` 357 | 358 | 参数: 359 | 360 | 无 361 | 362 | 返回值: 363 | 364 | 无 365 | 366 | **uplink_main** 367 | 368 | > 业务功能: 369 | > 370 | > - 数据上行业务主函数,读取串口数据解析后上传云端 371 | 372 | 例: 373 | 374 | ```python 375 | # Start uplink transaction 376 | try: 377 | _thread.start_new_thread(up_transaction.uplink_main, ()) 378 | except: 379 | raise self.Error(self.error_map[self.ErrCode.ESYS]) 380 | ``` 381 | 382 | 参数: 383 | 384 | 无 385 | 386 | 返回值: 387 | 388 | 无 389 | 390 | ### 3.3. `settings.py` 391 | 392 | #### 3.3.1. `Settings()`类 393 | 394 | **settings 导入** 395 | 396 | 例: 397 | 398 | ```python 399 | from usr.settings import settings 400 | ``` 401 | 402 | **init** 403 | 404 | > 功能: 405 | > 406 | > - 检查持久化配置文件(dtu_config.json)是否存在,存在则直接读取配置文件配置 407 | > - 若不存在则读取`SYSConfig`设置参数, 根据配置读取用户配置与功能配置 408 | > - 读取完成所有配置参数后,将配置参数写入配置文件中持久化存储 409 | 410 | 例: 411 | 412 | ```python 413 | res = settings.init() 414 | ``` 415 | 416 | 参数: 417 | 418 | 无 419 | 420 | 返回值: 421 | 422 | |数据类型|说明| 423 | |:---|---| 424 | |BOOL|`True`成功, `False`失败| 425 | 426 | **get** 427 | 428 | 例: 429 | 430 | ```python 431 | current_settings = settings.get() 432 | ``` 433 | 434 | 参数: 435 | 436 | 无 437 | 438 | 返回值: 439 | 440 | |数据类型|说明| 441 | |:---|---| 442 | |DICT|配置参数| 443 | 444 | #### save 持久化保存配置参数 445 | 446 | > 将配置参数写入文件进行持久化保存,文件名全路径`/usr/dtu_config.json` 447 | 448 | 例: 449 | 450 | ```python 451 | res = settings.save() 452 | ``` 453 | 454 | 参数: 455 | 456 | 无 457 | 458 | 返回值: 459 | 460 | |数据类型|说明| 461 | |:---|---| 462 | |BOOL|`True`成功, `False`失败| 463 | 464 | #### remove 删除配置参数文件 465 | 466 | 例: 467 | 468 | ```python 469 | res = settings.remove() 470 | ``` 471 | 472 | 参数: 473 | 474 | 无 475 | 476 | 返回值: 477 | 478 | |数据类型|说明| 479 | |:---|---| 480 | |BOOL|`True`成功, `False`失败| 481 | 482 | #### reset 重置配置参数 483 | 484 | > 先移除配置参数文件, 再重新生成配置参数文件 485 | 486 | 例: 487 | 488 | ```python 489 | res = settings.reset() 490 | ``` 491 | 492 | 参数: 493 | 494 | 无 495 | 496 | 返回值: 497 | 498 | |数据类型|说明| 499 | |:---|---| 500 | |BOOL|`True`成功, `False`失败| 501 | 502 | ### 3.4. `settings_user.py` 503 | 504 | #### 3.3.1. `UserConfig()`类 505 | 包含dtu_config.json中所有的配置项,当初始化配置参数时,如果没有找到dtu_config.json时则获取UserConfig类中元素的赋值。 506 | 507 | ## 4. 业务流程框架图 508 | 509 | ### 4.1 DTU功能框架图 510 | ![](./media/dtu_frame_diagram.jpg) 511 | 512 | ### 4.2 OTA升级流程图 513 | ![](./media/dtu_ota_flow_diagram.jpg) 514 | `上图为阿里云OTA升级流程,由于移远云创建升级计划后不会主动向模块发送升级计划,模块需要增加周期性请求OTA升级计划。` -------------------------------------------------------------------------------- /code/modules/socketIot.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :tcp.py 20 | @author :Elian.wang (elian.wang@quectel.com) 21 | @version :1.0.0 22 | @date :2022-08-05 10:48:43 23 | @copyright :Copyright (c) 2022 24 | """ 25 | try: 26 | import usys # type: ignore 27 | except ImportError: 28 | import sys as usys 29 | import utime 30 | import _thread 31 | import usocket 32 | from usr.modules.logging import getLogger 33 | from usr.modules.common import CloudObservable 34 | from usr.modules.common import option_lock, Singleton 35 | 36 | logger = getLogger(__name__) 37 | 38 | _socket_lock = _thread.allocate_lock() 39 | 40 | class Socket(CloudObservable): 41 | """This class is tcp socket""" 42 | 43 | def __init__(self, ip_type=None, protocol = "TCP", keep_alive=None, domain=None, port=None): 44 | super().__init__() 45 | if protocol == "TCP" or protocol == None: 46 | self.__protocol = "TCP" 47 | else: 48 | self.__protocol = "UDP" 49 | if ip_type == "IPv6": 50 | self.__ip_type = usocket.AF_INET6 51 | else: 52 | self.__ip_type = usocket.AF_INET 53 | self.__port = port 54 | self.__domain = domain 55 | self.__addr = None 56 | self.__socket = None 57 | self.__socket_args = [] 58 | self.__timeout = 50 59 | self.__keep_alive = keep_alive 60 | self.__listen_thread_id = None 61 | self.__init_addr() 62 | self.__init_socket() 63 | 64 | def __init_addr(self): 65 | """Get ip and port from domain. 66 | 67 | Raises: 68 | ValueError: Domain DNS parsing falied. 69 | """ 70 | if self.__domain is not None: 71 | if self.__port is None: 72 | self.__port = 8883 if self.__domain.startswith("https://") else 1883 73 | try: 74 | addr_info = usocket.getaddrinfo(self.__domain, self.__port) 75 | self.__ip = addr_info[0][-1][0] 76 | except Exception as e: 77 | usys.print_exception(e) # type: ignore 78 | raise ValueError("Domain %s DNS parsing error. %s" % (self.__domain, str(e))) 79 | self.__addr = (self.__ip, self.__port) 80 | 81 | def __init_socket(self): 82 | """Init socket by ip, port and method 83 | 84 | Raises: 85 | ValueError: ip or domain or method is illegal. 86 | """ 87 | if self.__protocol == 'TCP': 88 | socket_type = usocket.SOCK_STREAM 89 | socket_proto = usocket.IPPROTO_TCP 90 | elif self.__protocol == 'UDP': 91 | socket_type = usocket.SOCK_DGRAM 92 | socket_proto = usocket.IPPROTO_UDP 93 | else: 94 | raise ValueError("Args method is TCP or UDP, not %s" % self.__protocol) 95 | self.__socket_args = (self.__ip_type, socket_type, socket_proto) 96 | 97 | @option_lock(_socket_lock) 98 | def __connect(self): 99 | """Socket connect when method is TCP 100 | 101 | Returns: 102 | bool: True - success, False - falied 103 | """ 104 | if self.__socket_args: 105 | try: 106 | self.__socket = usocket.socket(*self.__socket_args) 107 | if self.__protocol == "TCP": 108 | if ( 109 | self.__addr is None 110 | or not isinstance(self.__addr, tuple) 111 | or None in self.__addr 112 | ): 113 | raise ValueError("Socket address is not properly initialized: %s" % str(self.__addr)) 114 | self.__socket.connect(self.__addr) 115 | return True 116 | except Exception as e: 117 | usys.print_exception(e) # type: ignore 118 | 119 | return False 120 | 121 | @option_lock(_socket_lock) 122 | def __disconnect(self): 123 | """Socket disconnect 124 | 125 | Returns: 126 | bool: True - success, False - falied 127 | """ 128 | if self.__socket is not None: 129 | try: 130 | self.__socket.close() 131 | self.__socket = None 132 | return True 133 | except Exception as e: 134 | usys.print_exception(e) # type: ignore 135 | return False 136 | else: 137 | return True 138 | 139 | def __recv(self): 140 | """Read data by socket. 141 | """ 142 | while True: 143 | data = b"" 144 | try: 145 | if self.__socket is not None: 146 | data = self.__socket.recv(1024) 147 | except Exception as e: 148 | if e.args[0] != 110: 149 | logger.error("%s read falied. error: %s" % (self.__protocol, str(e))) 150 | break 151 | else: 152 | if data != b"": 153 | try: 154 | self.notifyObservers(self, *("raw_data", {"topic":None, "data":data} ) ) 155 | except Exception as e: 156 | logger.error("{}".format(e)) 157 | utime.sleep_ms(50) 158 | 159 | @option_lock(_socket_lock) 160 | def __send(self, data): 161 | """Send data by socket. 162 | 163 | Args: 164 | data(str): To be send data 165 | 166 | Returns: 167 | bool: True - success, False - falied. 168 | """ 169 | if self.__socket is not None: 170 | try: 171 | write_data_num = self.__socket.write(data) 172 | if write_data_num == len(data): 173 | return True 174 | except Exception as e: 175 | usys.print_exception(e) # type: ignore 176 | 177 | return False 178 | 179 | def __listen(self): 180 | self.__listen_thread_id = _thread.start_new_thread(self.__recv, ()) 181 | 182 | def init(self, enforce=False): 183 | """Socket connect and register receive thread 184 | 185 | Args: 186 | enforce (bool, optional): Whether to force initialization. Defaults to False. 187 | 188 | Returns: 189 | bool: True - success, False - falied. 190 | """ 191 | if enforce is False: 192 | if self.get_status() == 0: 193 | return True 194 | if self.__socket is not None: 195 | try: 196 | self.__disconnect() 197 | except Exception as e: 198 | logger.error("tcp disconnect falied. %s" % e) 199 | try: 200 | if self.__listen_thread_id is not None: 201 | _thread.stop_thread(self.__listen_thread_id) # type: ignore 202 | except Exception as e: 203 | logger.error("stop listen thread falied. %s" % e) 204 | 205 | # FIX: when connect failed we return False instead of raise Exception for another try(self.init when post data.) 206 | if not self.__connect(): 207 | return False 208 | 209 | if self.__socket is None: 210 | logger.error("socket connect failed.") 211 | raise Exception("socket connect failed") 212 | 213 | if self.__keep_alive != 0: 214 | try: 215 | self.__socket.setsockopt(usocket.SOL_SOCKET, usocket.TCP_KEEPALIVE, self.__keep_alive) 216 | except Exception as e: 217 | self.__socket.close() 218 | logger.error("socket option set error:", e) 219 | raise Exception("socket option set error") 220 | self.__socket.settimeout(self.__timeout) 221 | # Start receive socket data 222 | self.__listen() 223 | logger.debug("self.get_status(): %s" % self.get_status()) 224 | if self.get_status() == 0: 225 | return True 226 | else: 227 | return False 228 | 229 | def get_status(self): 230 | """Get socket connection status 231 | 232 | Returns: 233 | [int]: 234 | -1: Error 235 | 0: Connected 236 | 1: Connecting 237 | 2: Disconnect 238 | """ 239 | _status = -1 240 | if self.__socket is not None: 241 | try: 242 | if self.__protocol == "TCP": 243 | socket_sta = self.__socket.getsocketsta() 244 | if socket_sta in range(4): 245 | # Connecting 246 | _status = 1 247 | elif socket_sta == 4: 248 | # Connected 249 | _status = 0 250 | elif socket_sta in range(5, 11): 251 | # Disconnect 252 | _status = 2 253 | elif self.__protocol == "UDP": 254 | _status = 0 255 | except Exception as e: 256 | usys.print_exception(e) # type: ignore 257 | return _status 258 | 259 | def through_post_data(self, data, topic_id): 260 | return self.__send(data) 261 | 262 | def post_data(self, data): 263 | return False 264 | 265 | def ota_request(self): 266 | return False 267 | 268 | def ota_action(self, action, module=None): 269 | return False 270 | 271 | def device_report(self): 272 | return False 273 | 274 | -------------------------------------------------------------------------------- /code/dtu.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :dtu.py 20 | @author :elian.wang@quectel.com 21 | @brief :dtu main function 22 | @version :0.1 23 | @date :2022-05-18 09:12:37 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | import _thread 28 | import modem 29 | import osTimer 30 | from usr.modules.common import Singleton 31 | from usr.modules.aliyunIot import AliYunIot 32 | from usr.modules.mqttIot import MqttIot 33 | from usr.modules.huawei_cloud import HuaweiIot 34 | from usr.modules.txyunIot import TXYunIot 35 | from usr.modules.socketIot import Socket 36 | 37 | from usr.settings import settings 38 | from usr.modules.serial import Serial 39 | from usr.modules.history import History 40 | from usr.modules.logging import getLogger 41 | from usr.dtu_transaction import DownlinkTransaction, OtaTransaction, UplinkTransaction, GuiToolsInteraction, ConfigTransaction 42 | from usr.modules.remote import RemotePublish, RemoteSubscribe 43 | from usr.settings import PROJECT_NAME, PROJECT_VERSION, DEVICE_FIRMWARE_NAME, DEVICE_FIRMWARE_VERSION 44 | 45 | log = getLogger(__name__) 46 | 47 | 48 | class Dtu(Singleton): 49 | """Dtu main function call 50 | """ 51 | def __init__(self): 52 | self.__ota_timer = osTimer() # type: ignore 53 | self.__ota_transaction = None 54 | 55 | def __cloud_init(self, protocol): 56 | """Cloud initialization and connection 57 | 58 | Args: 59 | protocol (str): cloud type name 60 | 61 | Returns: 62 | object: cloud object 63 | """ 64 | if protocol == "aliyun": 65 | cloud_config = settings.current_settings.get("aliyun_config") 66 | if cloud_config is None: 67 | raise Exception("Couldn't get setiings") 68 | client_id = cloud_config["client_id"] if cloud_config.get("client_id") else modem.getDevImei() 69 | cloud = AliYunIot(cloud_config.get("PK"), 70 | cloud_config.get("PS"), 71 | cloud_config.get("DK"), 72 | cloud_config.get("DS"), 73 | cloud_config.get("server"), 74 | int(cloud_config.get("qos", 0)), 75 | client_id, 76 | cloud_config.get("publish"), 77 | cloud_config.get("subscribe"), 78 | cloud_config.get("burning_method"), 79 | cloud_config.get("keep_alive"), 80 | mcu_name=PROJECT_NAME, 81 | mcu_version=PROJECT_VERSION, 82 | firmware_name=DEVICE_FIRMWARE_NAME, 83 | firmware_version=DEVICE_FIRMWARE_VERSION 84 | ) 85 | cloud.init(enforce=True) 86 | return cloud 87 | elif protocol == "txyun": 88 | cloud_config = settings.current_settings.get("txyun_config") 89 | if cloud_config is None: 90 | raise Exception("Couldn't get setiings") 91 | client_id = cloud_config["client_id"] if cloud_config.get("client_id") else modem.getDevImei() 92 | cloud = TXYunIot(cloud_config.get("PK"), 93 | cloud_config.get("PS"), 94 | cloud_config.get("DK"), 95 | cloud_config.get("DS"), 96 | cloud_config.get("clean_session", False), 97 | client_id, 98 | cloud_config.get("publish"), 99 | cloud_config.get("subscribe"), 100 | cloud_config.get("burning_method"), 101 | cloud_config.get("keep_alive"), 102 | mcu_name=PROJECT_NAME, 103 | mcu_version=PROJECT_VERSION, 104 | firmware_name=DEVICE_FIRMWARE_NAME, 105 | firmware_version=DEVICE_FIRMWARE_VERSION 106 | ) 107 | cloud.init(enforce=True) 108 | return cloud 109 | elif protocol == "hwyun": 110 | cloud_config = settings.current_settings.get("hwyun_config") 111 | if cloud_config is None: 112 | raise Exception("Couldn't get setiings") 113 | client_id = cloud_config["client_id"] if cloud_config.get("client_id") else modem.getDevImei() 114 | cloud = HuaweiIot(cloud_config.get("PK", None), 115 | cloud_config.get("PS", None), 116 | cloud_config.get("DK", None), 117 | cloud_config.get("DS", None), 118 | cloud_config.get("server", None), 119 | int(cloud_config.get("qos", 0)), 120 | int(cloud_config.get("port", 1883)), 121 | cloud_config.get("clean_session"), 122 | client_id, 123 | cloud_config.get("publish"), 124 | cloud_config.get("subscribe"), 125 | cloud_config.get("keep_alive"), 126 | mcu_name=PROJECT_NAME, 127 | mcu_version=PROJECT_VERSION, 128 | firmware_name=DEVICE_FIRMWARE_NAME, 129 | firmware_version=DEVICE_FIRMWARE_VERSION 130 | ) 131 | cloud.init(enforce=True) 132 | return cloud 133 | elif protocol.startswith("mqtt"): 134 | cloud_config = settings.current_settings.get("mqtt_private_cloud_config") 135 | if cloud_config is None: 136 | raise Exception("Couldn't get setiings") 137 | client_id = cloud_config["client_id"] if cloud_config.get("client_id") else modem.getDevImei() 138 | cloud = MqttIot(cloud_config.get("server", None), 139 | int(cloud_config.get("qos", 0)), 140 | int(cloud_config.get("port", 1883)), 141 | cloud_config.get("clean_session"), 142 | client_id, 143 | cloud_config.get("username"), 144 | cloud_config.get("password"), 145 | cloud_config.get("publish"), 146 | cloud_config.get("subscribe"), 147 | cloud_config.get("keep_alive") 148 | ) 149 | cloud.init(enforce=True) 150 | return cloud 151 | elif protocol.startswith("tcp"): 152 | cloud_config = settings.current_settings.get("tcp_private_cloud_config") 153 | if cloud_config is None: 154 | raise Exception("Couldn't get setiings") 155 | cloud = Socket(ip_type = cloud_config.get("ip_type"), 156 | keep_alive = cloud_config.get("keep_alive"), 157 | domain = cloud_config.get("server"), 158 | port = int(cloud_config.get("port")), 159 | ) 160 | cloud.init(enforce=True) 161 | return cloud 162 | 163 | def __periodic_ota_check(self, args): 164 | """Periodically check whether cloud have an upgrade plan""" 165 | if self.__ota_transaction is None: 166 | raise Exception("OTA check failed") 167 | self.__ota_transaction.ota_check() 168 | 169 | def start(self): 170 | """ 171 | Initializes and starts the DTU (Data Transfer Unit) system components. 172 | 173 | """ 174 | log.info("PROJECT_NAME: %s, PROJECT_VERSION: %s" % (PROJECT_NAME, PROJECT_VERSION)) 175 | log.info("DEVICE_FIRMWARE_NAME: %s, DEVICE_FIRMWARE_VERSION: %s" % (DEVICE_FIRMWARE_NAME, DEVICE_FIRMWARE_VERSION)) 176 | 177 | uart_setting = settings.current_settings["uart_config"] 178 | uart2_setting = settings.current_settings["uart_secondary_config"] 179 | 180 | # Serial initialization 181 | serial = Serial(int(uart_setting.get("port")), 182 | int(uart_setting.get("baudrate")), 183 | int(uart_setting.get("databits")), 184 | int(uart_setting.get("parity")), 185 | int(uart_setting.get("stopbits")), 186 | int(uart_setting.get("flowctl")), 187 | uart_setting.get("rs485_direction_pin")) 188 | serial2 = Serial(int(uart2_setting.get("port")), 189 | int(uart2_setting.get("baudrate")), 190 | int(uart2_setting.get("databits")), 191 | int(uart2_setting.get("parity")), 192 | int(uart2_setting.get("stopbits")), 193 | int(uart2_setting.get("flowctl")), 194 | uart2_setting.get("rs485_direction_pin")) 195 | 196 | # Cloud initialization 197 | cloud = self.__cloud_init(settings.current_settings["system_config"]["cloud"]) 198 | if cloud is None: 199 | raise Exception("Cloud init failed") 200 | # GuiToolsInteraction initialization 201 | gui_tool_inter = GuiToolsInteraction() 202 | # UplinkTransaction initialization 203 | up_transaction = UplinkTransaction() 204 | up_transaction.add_module(serial) 205 | 206 | config_transaction = ConfigTransaction() 207 | config_transaction.add_module(serial2) 208 | config_transaction.add_module(gui_tool_inter) 209 | # DownlinkTransaction initialization 210 | down_transaction = DownlinkTransaction() 211 | down_transaction.add_module(serial) 212 | 213 | # OtaTransaction initialization 214 | ota_transaction = OtaTransaction() 215 | 216 | # RemoteSubscribe initialization 217 | remote_sub = RemoteSubscribe() 218 | remote_sub.add_executor(down_transaction, 1) 219 | remote_sub.add_executor(ota_transaction, 2) 220 | cloud.addObserver(remote_sub) 221 | 222 | # RemotePublish initialization 223 | remote_pub = RemotePublish() 224 | remote_pub.add_cloud(cloud) 225 | up_transaction.add_module(remote_pub) 226 | ota_transaction.add_module(remote_pub) 227 | 228 | # History initialization 229 | if settings.current_settings["system_config"]["base_function"]["offline_storage"]: 230 | history = History() 231 | remote_pub.addObserver(history) 232 | up_transaction.add_module(history) 233 | # Send history data to the cloud after being powered on 234 | up_transaction.report_history() 235 | 236 | # Send module release information to cloud. After receiving this information, 237 | # the cloud server checks whether to upgrade modules 238 | ota_transaction.ota_check() 239 | # Periodically check whether cloud have an upgrade plan 240 | self.__ota_transaction = ota_transaction 241 | self.__ota_timer.start(1000 * 600, 1, self.__periodic_ota_check) 242 | # Start uplink transaction 243 | try: 244 | _thread.start_new_thread(up_transaction.uplink_main, ()) 245 | _thread.start_new_thread(config_transaction.config_main, ()) 246 | except: 247 | raise self.Error(self.error_map[self.ErrCode.ESYS]) # type: ignore 248 | 249 | 250 | if __name__ == "__main__": 251 | dtu = Dtu() 252 | dtu.start() 253 | 254 | -------------------------------------------------------------------------------- /docs/en/DTU_Solution_Development_Guide.md: -------------------------------------------------------------------------------- 1 | # DTU Solution Development Guide 2 | 3 | ## 1. Overview 4 | 5 | This document primarily introduces the file directory and interface usage instructions of the DTU project, facilitating customers to quickly familiarize themselves with the DTU scheme. 6 | 7 | ## 2. Project Directory 8 | ``` 9 | |--code 10 | |--dtu_config.json 11 | |--dtu.py 12 | |--dtu_transaction.py 13 | |--settings.py 14 | |--settings_user.py 15 | |--modules 16 | |--aliyunIot.py 17 | |--common.py 18 | |--history.py 19 | |--huawei_cloud.py 20 | |--logging.py 21 | |--mqttIot.py 22 | |--quecthing.py 23 | |--remote.py 24 | |--socketIot.py 25 | |--txyunIot.py 26 | |--serial.py 27 | ``` 28 | 29 | | Filename | Description | 30 | |---------- |----------------------------------------- | 31 | |`dtu_config.json` | Configuration file, user configuration parameters| 32 | |`dtu.py`| Main DTU file, contains all initialization logic for DTU| 33 | |`dtu_transaction.py`| DTU business logic file, all business logic related to DTU is in this file| 34 | |`settings.py`| Configuration parameter read/write module| 35 | |`settings_user.py`| Default configuration parameters, used when the DTU initialization does not find the dtu_config.json file| 36 | |`modules.aliyunIot.py`| Aliyun module, mainly used for message interaction with the cloud and OTA upgrades| 37 | |`modules.common.py`| General interface library| 38 | |`modules.history.py`| Historical file read/write operation module| 39 | |`modules.huawei_cloud.py`| Huawei Cloud module, mainly used for message interaction with the cloud| 40 | |`modules.logging.py`| QuecPython log printing interface| 41 | |`modules.mqttIot.py`| MQTT private cloud interface, for communication with the cloud| 42 | |`modules.quecthing.py`| Quectel Cloud module, mainly used for message interaction with the cloud and OTA upgrades| 43 | |`modules.remote.py`| Middleware between cloud and business logic, isolates business and cloud interfaces| 44 | |`modules.socketIot.py`| TCP protocol communication module, mainly used for message interaction with the cloud| 45 | |`modules.txyunIot.py`| Tencent Cloud module, mainly used for message interaction with the cloud| 46 | |`modules.serial.py`| Serial communication interface| 47 | *Note: The files under the modules folder are general interface files and usually do not need modification.* 48 | 49 | ## 3. Business API Function Description 50 | 51 | ### 3.1. `dtu.py` 52 | The main DTU file, contains all initialization logic for DTU. When `dtu.py` is executed, it calls: 53 | ```python 54 | if __name__ == "__main__": 55 | dtu = Dtu() 56 | dtu.start() 57 | ``` 58 | The `Dtu` class completes initialization and starts DTU business execution. 59 | #### 3.1.1. `Dtu` Class 60 | Contains the initialization of all main DTU modules. 61 | **cloud_init** 62 | 63 | > Business Function: 64 | > 65 | > Completes cloud initialization and connects to the cloud server. 66 | 67 | Example: 68 | 69 | ```python 70 | cloud = self.cloud_init(settings.current_settings["system_config"]["cloud"]) 71 | ``` 72 | 73 | Parameters: 74 | 75 | | Parameter | Type | Description | 76 | |---|---|---| 77 | | protocol | str | Cloud type name, such as `aliyun`| 78 | 79 | Return Value: 80 | 81 | |Data Type|Description| 82 | |:---|---| 83 | |object|Cloud object| 84 | 85 | **start** 86 | 87 | > Business Function: 88 | > 89 | > Completes the initialization of all DTU modules and starts DTU business. 90 | 91 | Example: 92 | 93 | ```python 94 | cloud = self.cloud_init(settings.current_settings["system_config"]["cloud"]) 95 | ``` 96 | 97 | Parameters: 98 | 99 | None 100 | 101 | Return Value: 102 | 103 | None 104 | 105 | ### 3.2. `dtu_transaction.py` 106 | 107 | #### 3.2.1. `DownlinkTransaction` Class 108 | DTU data downlink business, reads cloud information and sends it to the serial port. Registered as an actuator to the remote pub module, when the cloud sends data, it calls the `DownlinkTransaction` module for parsing. 109 | 110 | **add_module** 111 | 112 | > Registrable Module: 113 | > 114 | > `Serial` 115 | > 116 | > Business Function: 117 | > 118 | > Adds the serial module to the upstream data business module in a registered manner. 119 | 120 | Example: 121 | 122 | ```python 123 | # DownlinkTransaction initialization 124 | down_transaction = DownlinkTransaction() 125 | down_transaction.add_module(serial) 126 | ``` 127 | 128 | Parameters: 129 | 130 | | Parameter | Type | Description | 131 | |---|---|---| 132 | | module | object | Module object | 133 | 134 | Return Value: 135 | 136 | |Data Type|Description| 137 | |:---|---| 138 | |BOOL|`True` success, `False` failure| 139 | 140 | 141 | **__get_sub_topic_id** 142 | 143 | > Business Function: 144 | > 145 | > Subscribes to the Topic configuration from the cloud parameter configuration to find the Topic corresponding to the Topic id. 146 | 147 | Example: 148 | 149 | ```python 150 | # Get mqtt protocol message id 151 | cloud_type = settings.current_settings["system_config"]["cloud"] 152 | if cloud_type in ["aliyun", "txyun", "hwyun", "mqtt_private_cloud"]: 153 | msg_id = self.__get_sub_topic_id(kwargs.get("topic")) 154 | if msg_id == None: 155 | raise Exception("Not found correct topic id") 156 | ``` 157 | 158 | Parameters: 159 | 160 | | Parameter | Type | Description | 161 | |---|---|---| 162 | | topic | str | Subscribed Topic value received from the cloud | 163 | 164 | Return Value: 165 | 166 | |Data Type|Description| 167 | |:---|---| 168 | |str|Topic id| 169 | 170 | 171 | **downlink_main** 172 | 173 | > Business Function: 174 | > 175 | > Main function for data downlink business, receives cloud data and sends it from the serial port. 176 | 177 | Example: 178 | 179 | ```python 180 | def __raw_data(self, *args, **kwargs): 181 | """Handle cloud transparent data transmission.""" 182 | return self.__executor.downlink_main(*args, **kwargs) if self.__executor else False 183 | ``` 184 | 185 | Parameters: 186 | 187 | | Parameter | Type | Description | 188 | |---|---|---| 189 | | topic | str | Subscribed Topic value received from the cloud | 190 | 191 | Return Value: 192 | 193 | |Data Type|Description| 194 | |:---|---| 195 | |str|Topic id| 196 | 197 | #### 3.2.2. `OtaTransaction` Class 198 | Executes OTA business, cooperates with the cloud module's OTA interface to upgrade the module. 199 | Specific business logic is as follows: 200 | 1. The module powers on and uploads the module name and version number to the cloud, and the cloud records this information. 201 | 2. When the module name and version number have an upgrade plan, the cloud sends the upgrade plan information to the module. 202 | 3. The module checks the version information in the upgrade plan again. If the version number to be upgraded is different from the current module version number, it starts downloading the firmware. 203 | 4. After the firmware download is complete, it starts updating the firmware. After the firmware update is complete, the module restarts (**This process takes 1-2 minutes, do not reboot or power off**). 204 | 205 | **ota_check** 206 | 207 | > Business Function: 208 | > 209 | > Called during DTU initialization, sends the module name and version number to the cloud. 210 | 211 | Example: 212 | 213 | ```python 214 | # Send module release information to cloud. After receiving this information, 215 | # the cloud server checks whether to upgrade modules 216 | ota_transaction.ota_check() 217 | ``` 218 | 219 | Parameters: 220 | 221 | None 222 | 223 | Return Value: 224 | 225 | None 226 | 227 | **event_ota_plain** 228 | 229 | > Business Function: 230 | > 231 | > - This module is the `RemoteSubscribe` OTA upgrade plan information listener function. 232 | > - When a notification message is received from the listener, it processes it. 233 | > - Checks whether the device has enabled OTA upgrades, otherwise cancels the OTA upgrade. 234 | > - Calls the `Controller.remote_ota_action` function to perform the OTA upgrade. 235 | 236 | Example: 237 | 238 | ```python 239 | def __ota_plain(self, *args, **kwargs): 240 | """Handle cloud OTA plain""" 241 | return self.__ota_executor.event_ota_plain(*args, **kwargs) if self.__ota_executor else False 242 | ``` 243 | 244 | Parameters: 245 | 246 | None 247 | 248 | Return Value: 249 | 250 | None 251 | 252 | #### 3.2.3. `UplinkTransaction` Class 253 | DTU data uplink business, reads serial port information and uploads it to the cloud. 254 | 255 | **__get_pub_topic_id_list** 256 | 257 | > Business Function: 258 | > 259 | > - Gets the list of publish Topic ids from the cloud configuration. 260 | 261 | Example: 262 | 263 | ```python 264 | pub_topic_id_list = self.__get_pub_topic_id_list() 265 | ``` 266 | 267 | Parameters: 268 | 269 | None 270 | 271 | Return Value: 272 | 273 | |Data Type|Description| 274 | |:---|---| 275 | |list|List of publish Topic ids| 276 | 277 | **__parse** 278 | 279 | > Business Function: 280 | > 281 | > - When communicating with the cloud using the MQTT protocol, parses the serial data (when the communication protocol with the cloud is different, the serial protocol of the module is also different). 282 | > - This function can be recursive. When the serial data stream contains more than one complete frame of data, it will recursively continue to call the parsing. 283 | 284 | Example: 285 | 286 | ```python 287 | self.__parse_data += data 288 | self.__send_to_cloud_data = [] 289 | self.__parse() 290 | ``` 291 | 292 | Parameters: 293 | 294 | None 295 | 296 | Return Value: 297 | 298 | None 299 | 300 | **__uplink_data** 301 | 302 | > Business Function: 303 | > 304 | > - Parses and sends the data read from the serial port. 305 | > - When communicating with the cloud using the MQTT protocol, a new thread is created to execute the sending process when sending data to the cloud (the serial data may contain multiple frames of MQTT protocol, which takes a long time and may cause serial data read delay, so a separate thread is created to execute it). 306 | 307 | Example: 308 | 309 | ```python 310 | def uplink_main(self): 311 | """Read serial data, parse and upload to the cloud 312 | """ 313 | while 1: 314 | # Read uart data 315 | read_byte = self.__serial.read(nbytes=1024, timeout=100) 316 | if read_byte: 317 | try: 318 | self.__uplink_data(read_byte) 319 | except Exception as e: 320 | usys.print_exception(e) 321 | log.error("Parse uart data error: %s" % e) 322 | ``` 323 | 324 | Parameters: 325 | 326 | | Parameter | Type | Description | 327 | |---|---|---| 328 | | topic | str | Subscribed Topic value received from the cloud | 329 | 330 | Return Value: 331 | 332 | None 333 | 334 | **__post_history_data** 335 | 336 | > Business Function: 337 | > 338 | > - Sends historical data to the cloud. 339 | 340 | Example: 341 | 342 | ```python 343 | if hist["data"]: 344 | pt_count = 0 345 | for i, data in enumerate(hist["data"]): 346 | pt_count += 1 347 | if not self.__post_history_data(data): 348 | res = False 349 | break 350 | 351 | hist["data"] = hist["data"][pt_count:] 352 | if hist["data"]: 353 | # Flush data in hist-dictionary to tracker_data.hist file. 354 | self.__history.write(hist["data"]) 355 | ``` 356 | 357 | Parameters: 358 | 359 | None 360 | 361 | Return Value: 362 | 363 | None 364 | 365 | **uplink_main** 366 | 367 | > Business Function: 368 | > 369 | > - Main function for data uplink business, reads serial port data, parses it, and uploads it to the cloud. 370 | 371 | Example: 372 | 373 | ```python 374 | # Start uplink transaction 375 | try: 376 | _thread.start_new_thread(up_transaction.uplink_main, ()) 377 | except: 378 | raise self.Error(self.error_map[self.ErrCode.ESYS]) 379 | ``` 380 | 381 | Parameters: 382 | 383 | None 384 | 385 | Return Value: 386 | 387 | None 388 | 389 | ### 3.3. `settings.py` 390 | 391 | #### 3.3.1. `Settings` Class 392 | 393 | **settings Import** 394 | 395 | Example: 396 | 397 | ```python 398 | from usr.settings import settings 399 | ``` 400 | 401 | **init** 402 | 403 | > Function: 404 | > 405 | > - Checks if the persistent configuration file (dtu_config.json) exists. If it exists, it directly reads the configuration file. 406 | > - If it does not exist, it reads the `SYSConfig` settings parameters, reads user configuration and function configuration based on the configuration. 407 | > - After reading all configuration parameters, it writes the configuration parameters to the configuration file for persistent storage. 408 | 409 | Example: 410 | 411 | ```python 412 | res = settings.init() 413 | ``` 414 | 415 | Parameters: 416 | 417 | None 418 | 419 | Return Value: 420 | 421 | |Data Type|Description| 422 | |:---|---| 423 | |BOOL|`True` success, `False` failure| 424 | 425 | **get** 426 | 427 | Example: 428 | 429 | ```python 430 | current_settings = settings.get() 431 | ``` 432 | 433 | Parameters: 434 | 435 | None 436 | 437 | Return Value: 438 | 439 | |Data Type|Description| 440 | |:---|---| 441 | |DICT|Configuration parameters| 442 | 443 | #### save Persistent save configuration parameters 444 | 445 | > Writes the configuration parameters to the file for persistent storage, with the full path of the file name `/usr/dtu_config.json`. 446 | 447 | Example: 448 | 449 | ```python 450 | res = settings.save() 451 | ``` 452 | 453 | Parameters: 454 | 455 | None 456 | 457 | Return Value: 458 | 459 | |Data Type|Description| 460 | |:---|---| 461 | |BOOL|`True` success, `False` failure| 462 | 463 | #### remove Delete configuration parameter file 464 | 465 | Example: 466 | 467 | ```python 468 | res = settings.remove() 469 | ``` 470 | 471 | Parameters: 472 | 473 | None 474 | 475 | Return Value: 476 | 477 | |Data Type|Description| 478 | |:---|---| 479 | |BOOL|`True` success, `False` failure| 480 | 481 | #### reset Reset configuration parameters 482 | 483 | > First removes the configuration parameter file, then regenerates the configuration parameter file. 484 | 485 | Example: 486 | 487 | ```python 488 | res = settings.reset() 489 | ``` 490 | 491 | Parameters: 492 | 493 | None 494 | 495 | Return Value: 496 | 497 | |Data Type|Description| 498 | |:---|---| 499 | |BOOL|`True` success, `False` failure| 500 | 501 | ### 3.4. `settings_user.py` 502 | 503 | #### 3.3.1. `UserConfig` Class 504 | Contains all configuration items in dtu_config.json. When initializing configuration parameters, if the dtu_config.json is not found, the values of the elements in the `UserConfig` class are used. 505 | 506 | ## 4. Business Process Framework Diagram 507 | 508 | ### 4.1 DTU Function Framework Diagram 509 | ![](./media/dtu_frame_diagram.jpg) 510 | 511 | ### 4.2 OTA Upgrade Flow Diagram 512 | ![](./media/dtu_ota_flow_diagram.jpg) 513 | `The above diagram is the Aliyun OTA upgrade process. Since Quectel Cloud does not actively send upgrade plans to the module after creating the upgrade plan, the module needs to periodically request the OTA upgrade plan.` -------------------------------------------------------------------------------- /code/dtu_transaction.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env python 16 | # -*- coding: utf-8 -*- 17 | 18 | """ 19 | @file :dtu_transaction.py 20 | @author :elian.wang@quectel.com 21 | @brief :Dtu transaction related interfaces 22 | @version :0.1 23 | @date :2022-08-04 11:33:23 24 | @copyright :Copyright (c) 2022 25 | """ 26 | 27 | 28 | # import log 29 | import sim 30 | import net 31 | try: 32 | import usys as sys # type: ignore 33 | except ImportError: 34 | import sys 35 | import ujson 36 | import utime 37 | import modem 38 | import _thread 39 | from misc import Power 40 | from usr.modules.common import Singleton 41 | from usr.modules.logging import getLogger 42 | from usr.modules.serial import Serial 43 | from usr.modules.remote import RemotePublish 44 | from usr.modules.history import History 45 | from usr.settings import settings 46 | from usr.settings import PROJECT_NAME, PROJECT_VERSION, DEVICE_FIRMWARE_NAME, DEVICE_FIRMWARE_VERSION 47 | 48 | log = getLogger(__name__) 49 | 50 | class DownlinkTransaction(Singleton): 51 | """Data downlink:Receive data from the cloud and send it to serial 52 | """ 53 | def __init__(self) -> None: 54 | self.__serial: Serial | None = None 55 | 56 | def add_module(self, module: Serial, callback=None) -> bool: 57 | if isinstance(module, Serial): 58 | self.__serial = module 59 | return True 60 | return False 61 | 62 | def __get_sub_topic_id(self, topic): 63 | """Locate the topic id from the cloud setting 64 | 65 | Args: 66 | topic (str): topic in mqtt protocol 67 | 68 | Returns: 69 | str: topic id 70 | """ 71 | cloud_name = settings.current_settings["system_config"]["cloud"] 72 | cloud_config = settings.current_settings.get(cloud_name + "_config") 73 | if cloud_config == None: 74 | raise Exception("Cloud config parameter error") 75 | for k, v in cloud_config.get("subscribe", {}).items(): 76 | if topic == v: 77 | return k 78 | 79 | def downlink_main(self, *args, **kwargs): 80 | """Parsing cloud data, send to serial port 81 | 82 | Args: 83 | args (tuple): Not use 84 | kwargs (dict): The data received by the cloud,contains topic and data 85 | """ 86 | # Get mqtt protocol message id 87 | cloud_type = settings.current_settings["system_config"]["cloud"] 88 | if cloud_type in ["aliyun", "txyun", "hwyun", "mqtt_private_cloud"]: 89 | msg_id = self.__get_sub_topic_id(kwargs.get("topic")) 90 | if msg_id == None: 91 | raise Exception("Not found correct topic id") 92 | elif cloud_type == "tcp_private_cloud": 93 | msg_id = None 94 | else: 95 | raise Exception("This cloud is not support now") 96 | 97 | if isinstance(kwargs["data"], bytes): 98 | data = kwargs["data"].decode() 99 | elif isinstance(kwargs["data"], dict): 100 | data = ujson.dumps(kwargs["data"]) 101 | elif isinstance(kwargs["data"], str): 102 | data = kwargs["data"] 103 | else: 104 | data = str(kwargs["data"]) 105 | 106 | if cloud_type == "tcp_private_cloud": 107 | packed_data = data 108 | else: 109 | packed_data = "%s,%s,%s".encode('utf-8') % (str(msg_id), str(len(data)), data) 110 | log.debug("Got data: {}".format(packed_data)) 111 | # Send packed data through serial 112 | if self.__serial is None: 113 | raise Exception("Serial not available") 114 | self.__serial.write(packed_data) 115 | 116 | 117 | class OtaTransaction(Singleton): 118 | """Device firmware OTA and project file OTA transaction 119 | """ 120 | def __init__(self): 121 | self.__remote_pub = None 122 | 123 | def __remote_ota_check(self): 124 | if not self.__remote_pub: 125 | raise TypeError("self.__remote_pub is not registered.") 126 | return self.__remote_pub.cloud_ota_check() 127 | 128 | def __remote_ota_action(self, action, module): 129 | if not self.__remote_pub: 130 | raise TypeError("self.__remote_pub is not registered.") 131 | return self.__remote_pub.cloud_ota_action(action, module) 132 | 133 | def __remote_device_report(self): 134 | if not self.__remote_pub: 135 | raise TypeError("self.__remote_pub is not registered.") 136 | return self.__remote_pub.cloud_device_report() 137 | 138 | def add_module(self, module, callback=None): 139 | if isinstance(module, RemotePublish): 140 | self.__remote_pub = module 141 | return True 142 | return False 143 | 144 | def ota_check(self): 145 | """After powering on, release module information and check for update 146 | """ 147 | print("ota_check") 148 | try: 149 | if settings.current_settings["system_config"]["base_function"]["fota"]: 150 | self.__remote_ota_check() 151 | self.__remote_device_report() 152 | utime.sleep(1) 153 | except Exception as e: 154 | log.error("periodic_ota_check fault", e) 155 | 156 | def event_ota_plain(self, *args, **kwargs): 157 | """Determine the parameters and perform the OTA plan 158 | 159 | Args: 160 | args (tuple): Ota parameters sent from the cloud 161 | kwargs (dict): None 162 | """ 163 | log.debug("ota_plain args: %s, kwargs: %s" % (str(args), str(kwargs))) 164 | current_settings = settings.get() 165 | 166 | if current_settings["system_config"]["cloud"] == "aliyun": 167 | if args and args[0]: 168 | if args[0][0] == "ota_cfg": 169 | module = args[0][1].get("module") 170 | target_version = args[0][1].get("version") 171 | if module == DEVICE_FIRMWARE_NAME and current_settings["system_config"]["base_function"]["fota"] == 1: 172 | source_version = DEVICE_FIRMWARE_VERSION 173 | elif module == PROJECT_NAME and current_settings["system_config"]["base_function"]["sota"] == 1: 174 | source_version = PROJECT_VERSION 175 | else: 176 | return 177 | if target_version != source_version: 178 | self.__remote_ota_action(action=1, module=module) 179 | else: 180 | log.error("Current Cloud Not Supported OTA!") 181 | 182 | class UplinkTransaction(Singleton): 183 | """Data uplink: read data from the serial and send it to cloud 184 | """ 185 | def __init__(self): 186 | self.__remote_pub = None 187 | self.__serial: Serial | None = None 188 | self.__history = None 189 | self.__gui_tools_interac: GuiToolsInteraction | None = None 190 | self.__parse_data = "" 191 | self.__send_to_cloud_data = [] 192 | 193 | def __remote_post_data(self, data=None, topic_id=None): 194 | if not self.__remote_pub: 195 | raise TypeError("self.__remote_pub is not registered.") 196 | return self.__remote_pub.post_data(data, topic_id) 197 | 198 | def __get_pub_topic_id_list(self): 199 | """Get the publish topic id list in setting 200 | 201 | Returns: 202 | str: publist topic id list 203 | """ 204 | cloud_name = settings.current_settings["system_config"]["cloud"] 205 | cloud_config = settings.current_settings.get(cloud_name + "_config") 206 | if cloud_config == None: 207 | raise Exception("Cloud config parameter error") 208 | else: 209 | return cloud_config.get("subscribe").keys() 210 | 211 | def __parse(self): 212 | """Recursive parse uart data 213 | """ 214 | params_list = self.__parse_data.split(",", 2) 215 | if len(params_list) < 3: # The received data is not a complete frame 216 | return 217 | topic_id = params_list[0] 218 | data_len = params_list[1] 219 | msg_data = params_list[2] 220 | pub_topic_id_list = self.__get_pub_topic_id_list() 221 | if int(data_len) > len(msg_data): # The received data is not a complete frame 222 | return 223 | elif int(data_len) < len(msg_data): # The received data may contain part of the data of another frame 224 | if topic_id in pub_topic_id_list: 225 | self.__send_to_cloud_data.append((topic_id, msg_data[:int(data_len)])) 226 | # Frame format: ,, 227 | frame_len = len(topic_id)+len(data_len)+int(data_len)+2 228 | self.__parse_data = self.__parse_data[frame_len:] 229 | self.__parse() 230 | else: 231 | self.__parse_data = "" # Read data format eroor,restart read 232 | return 233 | else: 234 | self.__parse_data = "" 235 | if topic_id in pub_topic_id_list: 236 | self.__send_to_cloud_data.append((topic_id, msg_data)) 237 | else: 238 | return 239 | 240 | def __mqtt_protocol_uart_data_parse(self, data): 241 | """When cloud is mqtt protocol, parse uart data. 242 | 243 | Args: 244 | data (str): Data read from uart 245 | 246 | Returns: 247 | list: topic_id and data tuple list 248 | """ 249 | self.__parse_data += data 250 | self.__send_to_cloud_data = [] 251 | self.__parse() 252 | 253 | def __send_mqtt_data(self, data): 254 | for send_data in data: 255 | self.__remote_post_data(data=send_data[1], topic_id=send_data[0]) 256 | utime.sleep_ms(10) 257 | 258 | def __uplink_data(self, data): 259 | """Parsing uart data, send data to cloud 260 | 261 | Args: 262 | data (bytes): data read from uart 263 | """ 264 | 265 | if settings.current_settings["system_config"]["cloud"] == "tcp_private_cloud": 266 | self.__remote_post_data(data=data) 267 | else: 268 | try: 269 | self.__mqtt_protocol_uart_data_parse(data) 270 | if len(self.__send_to_cloud_data) != 0: 271 | _thread.start_new_thread(self.__send_mqtt_data, (self.__send_to_cloud_data,)) 272 | except Exception as e: 273 | log.error(e) 274 | 275 | def __post_history_data(self, data): 276 | """Post history data to cloud 277 | 278 | Args: 279 | data (str): history data 280 | 281 | Returns: 282 | True: Successfully post 283 | False:Failure to post 284 | """ 285 | log.info("post_history_data") 286 | try: 287 | if settings.current_settings["system_config"]["cloud"] == "tcp_private_cloud_config": 288 | return self.__remote_post_data(data=data) 289 | else: 290 | # In this case, topic id is not used specified, topic id default 0. 291 | return self.__remote_post_data(data=data, topic_id=0) 292 | except Exception as e: 293 | log.error(e) 294 | return False 295 | 296 | def add_module(self, module, callback=None): 297 | if isinstance(module, RemotePublish): 298 | self.__remote_pub = module 299 | return True 300 | elif isinstance(module, Serial): 301 | self.__serial = module 302 | return True 303 | elif isinstance(module, History): 304 | self.__history = module 305 | return True 306 | elif isinstance(module, GuiToolsInteraction): 307 | self.__gui_tools_interac = module 308 | return True 309 | return False 310 | 311 | def uplink_main(self): 312 | """Read serial data, parse and upload to the cloud 313 | """ 314 | log.debug("Starting uplink loop") 315 | if self.__serial is None: 316 | raise Exception("Serial not available") 317 | while 1: 318 | # Read uart data 319 | read_byte = self.__serial.read(nbytes=1024, timeout=100) 320 | if read_byte: 321 | try: 322 | self.__uplink_data(read_byte) 323 | except Exception as e: 324 | sys.print_exception(e) # type: ignore 325 | log.error("Parse uart data error: %s" % e) 326 | log.debug("Exited uplink loop") 327 | 328 | def report_history(self) -> bool: 329 | """Report history data to cloud 330 | Returns: 331 | boolen: True: Successfully post 332 | False:Failure to post 333 | """ 334 | if not self.__history: 335 | raise TypeError("self.__history is not registered.") 336 | 337 | res = True 338 | hist = self.__history.read() 339 | print("hist[data]:", hist["data"]) 340 | 341 | if hist["data"]: 342 | pt_count = 0 343 | for i, data in enumerate(hist["data"]): 344 | pt_count += 1 345 | if not self.__post_history_data(data): 346 | res = False 347 | break 348 | 349 | hist["data"] = hist["data"][pt_count:] 350 | if hist["data"]: 351 | # Flush data in hist-dictionary to tracker_data.hist file. 352 | self.__history.write(hist["data"]) 353 | 354 | return res 355 | 356 | class ConfigTransaction(Singleton): 357 | """Data uplink: read data from the serial and apply configuration 358 | """ 359 | def __init__(self) -> None: 360 | self.__serial: Serial | None = None 361 | self.__gui_tools_interac: GuiToolsInteraction | None = None 362 | 363 | 364 | def __config_data(self, data) -> None: 365 | """Parsing uart data, check if it is a configuration command 366 | 367 | Args: 368 | data (bytes): data read from uart 369 | """ 370 | if self.__gui_tools_interac is None: 371 | raise Exception("GUI tools not available") 372 | gui_tool_ack: str = self.__gui_tools_interac.parse_serial_data(data) 373 | if gui_tool_ack: # GUI tools command data 374 | if self.__serial is None: 375 | raise Exception("Serial not available") 376 | self.__serial.write(gui_tool_ack) 377 | return 378 | 379 | 380 | def add_module(self, module, callback=None) -> bool: 381 | if isinstance(module, Serial): 382 | self.__serial = module 383 | return True 384 | elif isinstance(module, GuiToolsInteraction): 385 | self.__gui_tools_interac = module 386 | return True 387 | return False 388 | 389 | 390 | def config_main(self) -> None: 391 | """Read serial data, parse and upload to the cloud 392 | """ 393 | if self.__serial is None: 394 | raise Exception("Serial not available") 395 | while 1: 396 | # Read uart data 397 | read_byte = self.__serial.read(nbytes=1024, timeout=100) 398 | if read_byte: 399 | try: 400 | self.__config_data(read_byte) 401 | except Exception as e: 402 | sys.print_exception(e) # type: ignore 403 | log.error("Parse uart data error: %s" % e) 404 | 405 | 406 | class GuiToolsInteraction(): 407 | def __init__(self) -> None: 408 | self.__query_command: dict[int, str] = { 409 | 0: "get_imei", 410 | 1: "get_number", 411 | 2: "get_csq", 412 | 3: "get_cur_config", 413 | 4: "get_iccid", 414 | } 415 | self.__basic_setting_command: dict[int, str] = { 416 | 255: "restart", 417 | 50: "set_fota", 418 | 51: "set_sota", 419 | 52: "set_history_data", 420 | 53: "set_uart_conf", 421 | 54: "set_cloud_conf", 422 | 55: "retore_factory_setting", 423 | } 424 | 425 | def __get_imei(self, code, data): 426 | return {"code": code, "data": modem.getDevImei(), "status": 1} 427 | 428 | def __get_number(self, code, data): 429 | log.info(sim.getPhoneNumber()) 430 | return {"code": code, "data": sim.getPhoneNumber(), "status": 1} 431 | 432 | def __get_csq(self, code, data): 433 | return {"code": code, "data": net.csqQueryPoll(), "status": 1} 434 | 435 | def __get_cur_config(self, code, data): 436 | log.info("get_cur_config") 437 | current_settings = settings.get() 438 | return {"code": code, "data": current_settings, "status": 1} 439 | 440 | def __restart(self, code, data): 441 | log.info("Restarting...") 442 | Power.powerRestart() 443 | 444 | def __set_fota(self, code, data): 445 | try: 446 | settings.set("fota", data["fota"]) 447 | settings.save() 448 | return {"code": code, "status": 1} 449 | except Exception as e: 450 | log.error("e = {}".format(e)) 451 | return {"code": code, "status": 0} 452 | 453 | def __set_sota(self, code, data): 454 | try: 455 | settings.set("sota", data["sota"]) 456 | settings.save() 457 | return {"code": code, "status": 1} 458 | except Exception as e: 459 | log.error("e = {}".format(e)) 460 | return {"code": code, "status": 0} 461 | 462 | def __set_history_data(self, code, data): 463 | try: 464 | settings.set("offline_storage", data["offline_storage"]) 465 | settings.save() 466 | return {"code": code, "status": 1} 467 | except Exception as e: 468 | log.error("e = {}".format(e)) 469 | return {"code": code, "status": 0} 470 | 471 | def __set_uart_conf(self, code, data): 472 | try: 473 | uart_conf = data["uart_config"] 474 | if not isinstance(uart_conf, dict): 475 | raise Exception("Data type error") 476 | settings.set("uart_config", uart_conf) 477 | settings.save() 478 | return {"code": code, "status": 1} 479 | except Exception as e: 480 | log.error("e = {}".format(e)) 481 | return {"code": code, "status": 0} 482 | 483 | def __set_cloud_conf(self, code, data): 484 | try: 485 | cloud_type = data["cloud_type"] 486 | cloud_conf = data["cloud_conf"] 487 | if not isinstance(cloud_conf, dict): 488 | raise Exception("Data type error") 489 | settings.set(cloud_type+"_config", cloud_conf) 490 | settings.set("cloud", cloud_type) 491 | settings.save() 492 | return {"code": code, "status": 1} 493 | except Exception as e: 494 | log.error("e = {}".format(e)) 495 | return {"code": code, "status": 0} 496 | 497 | def __retore_factory_setting(self, code, data): 498 | try: 499 | settings.reset() 500 | Power.powerRestart() 501 | return {"code": code, "status": 1} 502 | except Exception as e: 503 | log.error("e = {}".format(e)) 504 | return {"code": code, "status": 0} 505 | 506 | def __exec_command_code(self, cmd_code, data=None): 507 | """Executes a command based on the provided command code and optional data. 508 | 509 | Checks if the given `cmd_code` exists in either the query command or basic setting command dictionaries. 510 | If found, it dynamically constructs the method name, retrieves the corresponding method, and executes it with the 511 | provided `cmd_code` and `data`. If the command code is not found in either dictionary, or if an exception occurs 512 | during execution, an error is logged and an error response is returned. 513 | 514 | Args: 515 | cmd_code (str): The command code to execute. 516 | data (optional): Additional data to pass to the command handler. 517 | Returns: 518 | Any: The result of the executed command handler, or an error dictionary if the command code is invalid or an exception occurs. 519 | """ 520 | 521 | ret = None 522 | if cmd_code in self.__query_command.keys(): 523 | try: 524 | cmd = "__" + self.__query_command.get(cmd_code, "") 525 | func = getattr(self, cmd) 526 | ret = func(cmd_code, data) 527 | except Exception as e: 528 | log.error("search_command_func_code_list:", e) 529 | elif cmd_code in self.__basic_setting_command.keys(): 530 | try: 531 | cmd = "__" + self.__basic_setting_command.get(cmd_code, "") 532 | func = getattr(self, cmd) 533 | ret = func(cmd_code, data) 534 | except Exception as e: 535 | log.error("basic_setting_command_list:", e) 536 | else: 537 | log.error("Command code error") 538 | ret = {"code": cmd_code, "status": 0, "error": "Command code error"} 539 | return ret 540 | 541 | def parse_serial_data(self, serial_data) -> str: 542 | """Parse uart data in the format specified by the GUI 543 | 544 | Args: 545 | gui_data (bytes): data read from uart 546 | sid (str): uart channel id 547 | 548 | Returns: 549 | True: GUI data was successfully obtained 550 | False: get GUI data failed 551 | """ 552 | print("serial data:", serial_data) 553 | data_list = serial_data.split(",", 2) 554 | if len(data_list) != 3: 555 | log.info("DTU CMD list length validate fail. CMD Parse end.") 556 | return "" 557 | gui_code = data_list[0] 558 | if gui_code != "99": 559 | return "" 560 | data_length = data_list[1] 561 | msg_data = data_list[2] 562 | try: 563 | data_len_int = int(data_length) 564 | except: 565 | log.error("DTU CMD data error.") 566 | return "" 567 | if len(msg_data) > data_len_int: 568 | log.error("DTU CMD length validate failed.") 569 | return "" 570 | elif len(msg_data) < data_len_int: 571 | log.info("Msg length shorter than length") 572 | return "" 573 | try: 574 | data = ujson.loads(msg_data) 575 | except Exception as e: 576 | log.error(e) 577 | return "" 578 | cmd_code = data.get("cmd_code") 579 | # No command code was obtained 580 | if cmd_code is None: 581 | return "" 582 | params_data = data.get("data") 583 | rec = self.__exec_command_code(int(cmd_code), data=params_data) 584 | rec_str = ujson.dumps(rec) 585 | rec_format = "99,{},{}".format(len(rec_str), rec_str) 586 | print("GUI CMD SUCCESS") 587 | return rec_format 588 | --------------------------------------------------------------------------------