From 924ab5b67c8598e675613d9a7e7f7c4ae9d2b53a Mon Sep 17 00:00:00 2001 From: "wangchengdong.wcd" Date: Fri, 24 Jun 2022 13:06:59 +0800 Subject: [PATCH] add linksdk and aliyunIoT python module --- .gitattributes | 2 +- .gitignore | 5 +- README.md | 195 +-- external/activation/include/activation.h | 2 +- external/activation/src/activation.c | 2 +- external/external.mk | 4 - .../lvgl/examples/libs/freetype/arial.ttf | Bin 311516 -> 311515 bytes .../lvgl/src/extra/libs/freetype/arial.ttf | Bin 311516 -> 311515 bytes lib/lv_bindings/lvgl/src/font/korean.ttf | Bin 3370514 -> 3370508 bytes modules/adaptor/esp32/c3/systemAdaptor.py | 43 + .../adaptor/esp32/m5stack/systemAdaptor.py | 43 + .../adaptor/esp32/nodemcu32s/systemAdaptor.py | 44 + modules/adaptor/esp32/s3/systemAdaptor.py | 43 + modules/adaptor/stm32/systemAdaptor.py | 21 + modules/config.cmake | 32 + modules/driver/adc.py | 16 +- modules/driver/gpio.py | 30 +- modules/driver/i2c.py | 20 +- modules/driver/spi.py | 39 +- modules/driver/uart.py | 44 +- modules/driver/wdt.py | 2 - modules/hmac/component.mk | 17 + modules/hmac/include/hmac_sha256.h | 35 + modules/hmac/include/sha256.h | 79 + modules/hmac/src/hmac_sha256.c | 87 ++ modules/hmac/src/modhmac.c | 49 + modules/hmac/src/sha256.c | 269 ++++ modules/modbus/__init__.py | 1 - modules/modbus/manifest.py | 11 +- modules/modbus/uModBus.py | 134 +- modules/modbus/uModBusFunctions.py | 4 +- modules/modbus/uModBusSerial.py | 6 +- modules/modbus/uModBusTCP.py | 6 +- modules/modules.mk | 9 +- modules/system/component.mk | 15 + modules/system/include/system.h | 6 + modules/system/modsystem.c | 98 -- modules/system/src/modsystem.c | 53 + .../aliyunIoT.py} | 92 +- modules/ukv/kv.py | 71 + modules/ulinksdk/{_uhttp => }/__init__.py | 0 modules/ulinksdk/_uhttp/client.py | 1333 ----------------- modules/ulinksdk/_uhttp/example_client.py | 9 - modules/ulinksdk/_umqtt/__init__.py | 0 modules/ulinksdk/_umqtt/errno.py | 22 - modules/ulinksdk/_umqtt/robust2.py | 121 -- modules/ulinksdk/_umqtt/simple2.py | 126 -- modules/ulinksdk/crc16.py | 43 - modules/ulinksdk/dynreg.py | 253 ++-- modules/ulinksdk/email/__init__.py | 0 modules/ulinksdk/email/errors.py | 129 -- modules/ulinksdk/email/feedparser.py | 519 ------- modules/ulinksdk/email/iterators.py | 70 - modules/ulinksdk/email/message.py | 878 ----------- modules/ulinksdk/email/parser.py | 127 -- .../{upload_file.py => fileupload.py} | 345 +++-- modules/ulinksdk/hashlib/__init__.py | 24 - modules/ulinksdk/hashlib/_sha224.py | 1 - modules/ulinksdk/hashlib/_sha256.py | 301 ---- modules/ulinksdk/hashlib/_sha384.py | 1 - modules/ulinksdk/hashlib/_sha512.py | 519 ------- modules/ulinksdk/hmac.py | 162 -- modules/ulinksdk/linksdk.py | 317 ++-- modules/ulinksdk/main.py | 214 --- modules/ulinksdk/manifest.py | 8 + modules/ulinksdk/urllib/__init__.py | 0 modules/ulinksdk/urllib/parse.py | 1150 -------------- modules/ulinksdk/warnings.py | 2 - modules/umqtt.robust/umqtt/robust2.py | 121 ++ modules/umqtt.simple/umqtt/simple2.py | 132 ++ modules/utility/component.mk | 17 + modules/utility/include/crc16.h | 29 + modules/utility/src/crc16_ibm.c | 41 + modules/utility/src/crc16_modbus.c | 20 + modules/utility/src/modutility.c | 59 + modules/w5500/w5500.py | 10 + mpy-cross/mpconfigport.h | 2 + ports/esp32/CMakeLists.txt | 40 - ports/esp32/Makefile | 13 +- ports/esp32/README.md | 6 + ports/esp32/board_config.h | 27 - ports/esp32/boards/ESP32_S2_WROVER/board.json | 19 + .../ESP32_S2_WROVER/mpconfigboard.cmake | 12 + .../boards/ESP32_S2_WROVER/mpconfigboard.h | 8 + .../boards/ESP32_S2_WROVER/sdkconfig.board | 11 + ports/esp32/boards/GENERIC/board.json | 22 + ports/esp32/boards/GENERIC/board.md | 3 + ports/esp32/boards/GENERIC/manifest copy.py | 32 + ports/esp32/boards/GENERIC/manifest.py | 34 + .../esp32/boards/GENERIC/manifest_release.py | 9 + .../esp32/boards/GENERIC/mpconfigboard.cmake | 6 +- ports/esp32/boards/GENERIC/mpconfigboard.h | 1 - ports/esp32/boards/GENERIC/sdkconfig.board | 23 - ports/esp32/boards/GENERIC_C3/board.json | 19 + ports/esp32/boards/GENERIC_C3/manifest.py | 34 + .../boards/GENERIC_C3/manifest_release.py | 9 + .../boards/GENERIC_C3/mpconfigboard.cmake | 3 +- ports/esp32/boards/GENERIC_C3/mpconfigboard.h | 1 - ports/esp32/boards/GENERIC_C3/sdkconfig.board | 35 - ports/esp32/boards/GENERIC_C3_USB/board.json | 19 + .../boards/GENERIC_C3_USB/mpconfigboard.h | 5 +- .../boards/GENERIC_C3_USB/sdkconfig.board | 4 +- ports/esp32/boards/GENERIC_D2WD/board.json | 19 + .../esp32/boards/GENERIC_D2WD/sdkconfig.board | 4 + ports/esp32/boards/GENERIC_OTA/board.json | 19 + ports/esp32/boards/GENERIC_S2/board.json | 18 + ports/esp32/boards/GENERIC_S3/board.json | 18 + ports/esp32/boards/GENERIC_S3/manifest.py | 34 + .../boards/GENERIC_S3/manifest_release.py | 9 + .../boards/GENERIC_S3/mpconfigboard.cmake | 3 +- ports/esp32/boards/GENERIC_S3/mpconfigboard.h | 11 +- ports/esp32/boards/GENERIC_S3/sdkconfig.board | 30 +- .../esp32/boards/GENERIC_S3_SPIRAM/board.json | 18 + .../GENERIC_S3_SPIRAM/mpconfigboard.cmake | 12 + .../boards/GENERIC_S3_SPIRAM/mpconfigboard.h | 8 + .../boards/GENERIC_S3_SPIRAM/sdkconfig.board | 12 + .../board.json | 4 +- .../board.md | 0 ports/esp32/boards/LOLIN_S2_MINI/board.json | 19 + ports/esp32/boards/LOLIN_S2_MINI/manifest.py | 2 + .../boards/LOLIN_S2_MINI/modules/s2mini.py | 31 + .../boards/LOLIN_S2_MINI/mpconfigboard.cmake | 11 + .../boards/LOLIN_S2_MINI/mpconfigboard.h | 12 + .../boards/LOLIN_S2_MINI/sdkconfig.board | 6 + ports/esp32/boards/LOLIN_S2_PICO/board.json | 22 + ports/esp32/boards/LOLIN_S2_PICO/manifest.py | 4 + .../boards/LOLIN_S2_PICO/modules/s2pico.py | 38 + .../LOLIN_S2_PICO/modules/s2pico_oled.py | 48 + .../boards/LOLIN_S2_PICO/mpconfigboard.cmake | 11 + .../boards/LOLIN_S2_PICO/mpconfigboard.h | 12 + .../boards/LOLIN_S2_PICO/sdkconfig.board | 6 + ports/esp32/boards/M5STACK_ATOM/board.json | 22 + .../boards/M5STACK_CORE2/mpconfigboard.cmake | 14 - .../boards/M5STACK_CORE2/mpconfigboard.h | 4 - .../boards/M5STACK_CORE2/sdkconfig.board | 57 - .../esp32/boards/M5STACK_CORE2/sdkconfig.lvgl | 183 --- ports/esp32/boards/SIL_WESP32/board.json | 22 + ports/esp32/boards/UM_FEATHERS2/board.json | 27 + ports/esp32/boards/UM_FEATHERS2/board.md | 2 + ports/esp32/boards/UM_FEATHERS2/deploy.md | 50 + ports/esp32/boards/UM_FEATHERS2NEO/board.json | 27 + ports/esp32/boards/UM_FEATHERS2NEO/board.md | 2 + ports/esp32/boards/UM_FEATHERS2NEO/deploy.md | 50 + .../esp32/boards/UM_FEATHERS2NEO/manifest.py | 2 + .../UM_FEATHERS2NEO/modules/feathers2neo.py | 90 ++ .../UM_FEATHERS2NEO/mpconfigboard.cmake | 8 + .../boards/UM_FEATHERS2NEO/mpconfigboard.h | 12 + .../boards/UM_FEATHERS2NEO/sdkconfig.board | 8 + ports/esp32/boards/UM_TINYPICO/board.json | 29 + ports/esp32/boards/UM_TINYPICO/board.md | 3 + ports/esp32/boards/UM_TINYPICO/deploy.md | 46 + ports/esp32/boards/UM_TINYS2/board.json | 26 + ports/esp32/boards/UM_TINYS2/board.md | 2 + ports/esp32/boards/UM_TINYS2/deploy.md | 50 + ports/esp32/boards/deploy.md | 14 + ports/esp32/boards/deploy_s2.md | 14 + ports/esp32/boards/manifest.py | 27 + ports/esp32/boards/sdkconfig.base | 6 +- ports/esp32/boards/sdkconfig.spiram | 2 - ports/esp32/boards/sdkconfig.spiram_sx | 4 + .../components/dummy_main/CMakeLists.txt | 2 - .../esp32/components/dummy_main/component.mk | 0 .../esp32/components/dummy_main/main_dummy.c | 6 - ports/esp32/esp32_rmt.c | 65 +- ports/esp32/fatfs_port.c | 8 + ports/esp32/font_petme128_8x8.h | 128 -- ports/esp32/fs/boot.py | 40 - ports/esp32/fs/lib/appOta.py | 150 -- ports/esp32/fs/lib/bleNetConfig.py | 130 -- ports/esp32/fs/lib/display_driver.py | 20 - ports/esp32/fs/lib/oneMinuteOnCloud.py | 347 ----- ports/esp32/libs/neopixel.c | 570 ------- ports/esp32/libs/neopixel.h | 71 - ports/esp32/m5stackcore2/boot.py | 39 - ports/esp32/m5stackcore2/lib/axp192.py | 248 --- ports/esp32/m5stackcore2/lib/i2c_bus.py | 197 --- ports/esp32/m5stackcore2/lib/pcm.py | 198 --- ports/esp32/m5stackcore2/lib/uai.py | 63 - ports/esp32/machine_adc.c | 297 ++-- ports/esp32/machine_adc.h | 16 - ports/esp32/machine_adcblock.c | 203 --- ports/esp32/machine_adcblock.h | 20 - ports/esp32/machine_bitstream.c | 130 +- ports/esp32/machine_pin.c | 37 +- ports/esp32/machine_pwm.c | 716 ++++++--- ports/esp32/main.c | 262 +--- ports/esp32/main/CMakeLists.txt | 49 +- ports/esp32/modesp32.h | 4 + ports/esp32/modmachine.c | 2 +- ports/esp32/modmachine.h | 4 +- ports/esp32/modnetwork.c | 640 +------- ports/esp32/modnetwork.h | 25 + ports/esp32/modules/uai.py | 63 - ports/esp32/moduos.c | 174 +-- ports/esp32/mpconfigport.h | 68 +- ports/esp32/mphalport.c | 2 - ports/esp32/mphalport.h | 21 +- ports/esp32/mpthreadport.c | 25 +- ports/esp32/network_lan.c | 9 +- ports/esp32/network_wlan.c | 574 +++++++ ports/esp32/partitions-16MiB-priv.csv | 11 - ports/esp32/partitions-8MiB.csv | 7 + ports/esp32/partitions.csv | 14 +- ports/esp32/uart.c | 39 +- ports/esp32/uart.h | 9 - ports/esp32/usb.c | 23 +- ports/esp32/utility.h | 26 - ports/esp8266/Makefile | 234 +++ ports/esp8266/README.md | 188 +++ ports/esp8266/boards/GENERIC/board.json | 19 + ports/esp8266/boards/GENERIC/board.md | 19 + ports/esp8266/boards/GENERIC/manifest.py | 21 + ports/esp8266/boards/GENERIC/mpconfigboard.h | 22 + ports/esp8266/boards/GENERIC/mpconfigboard.mk | 7 + ports/esp8266/boards/GENERIC_1M/board.json | 16 + ports/esp8266/boards/GENERIC_1M/board.md | 5 + .../esp8266/boards/GENERIC_1M/mpconfigboard.h | 20 + .../boards/GENERIC_1M/mpconfigboard.mk | 4 + ports/esp8266/boards/GENERIC_512K/_boot.py | 3 + ports/esp8266/boards/GENERIC_512K/board.json | 16 + ports/esp8266/boards/GENERIC_512K/board.md | 3 + ports/esp8266/boards/GENERIC_512K/manifest.py | 6 + .../boards/GENERIC_512K/mpconfigboard.h | 4 + .../boards/GENERIC_512K/mpconfigboard.mk | 3 + ports/esp8266/boards/deploy.md | 1 + ports/esp8266/boards/eagle.rom.addr.v6.ld | 351 +++++ ports/esp8266/boards/esp8266_1m.ld | 19 + ports/esp8266/boards/esp8266_2m.ld | 18 + ports/esp8266/boards/esp8266_512k.ld | 18 + ports/esp8266/boards/esp8266_common.ld | 319 ++++ ports/esp8266/boards/esp8266_ota.ld | 13 + ports/esp8266/boards/manifest.py | 6 + ports/esp8266/esp_init_data.c | 77 + ports/esp8266/esp_mphal.c | 186 +++ ports/esp8266/esp_mphal.h | 107 ++ ports/esp8266/espapa102.c | 115 ++ ports/esp8266/espapa102.h | 31 + ports/esp8266/esppwm.c | 426 ++++++ ports/esp8266/esppwm.h | 17 + ports/esp8266/ets_alt_task.c | 240 +++ ports/esp8266/ets_alt_task.h | 12 + ports/esp8266/etshal.h | 27 + ports/esp8266/fatfs_port.c | 43 + ports/esp8266/gccollect.c | 52 + ports/esp8266/gccollect.h | 46 + ports/esp8266/gchelper.s | 22 + ports/esp8266/help.c | 54 + ports/esp8266/hspi.c | 337 +++++ ports/esp8266/hspi.h | 79 + ports/esp8266/hspi_register.h | 280 ++++ ports/esp8266/lexerstr32.c | 69 + ports/esp8266/machine_adc.c | 98 ++ ports/esp8266/machine_bitstream.c | 74 + ports/esp8266/machine_hspi.c | 188 +++ ports/esp8266/machine_pin.c | 517 +++++++ ports/esp8266/machine_pwm.c | 135 ++ ports/esp8266/machine_rtc.c | 270 ++++ ports/esp8266/machine_uart.c | 332 ++++ ports/esp8266/machine_wdt.c | 85 ++ ports/esp8266/main.c | 177 +++ ports/esp8266/makeimg.py | 54 + ports/esp8266/modesp.c | 382 +++++ ports/esp8266/modmachine.c | 457 ++++++ ports/esp8266/modmachine.h | 38 + ports/esp8266/modnetwork.c | 547 +++++++ ports/esp8266/modpyb.c | 79 + ports/esp8266/modules/_boot.py | 15 + ports/esp8266/modules/apa102.py | 17 + ports/esp8266/modules/flashbdev.py | 42 + ports/esp8266/modules/inisetup.py | 64 + ports/{esp32 => esp8266}/modules/ntptime.py | 0 ports/esp8266/modules/port_diag.py | 38 + ports/esp8266/moduos.c | 139 ++ ports/esp8266/modutime.c | 131 ++ ports/esp8266/mpconfigport.h | 214 +++ .../utility.c => esp8266/posix_helpers.c} | 64 +- ports/esp8266/qstrdefsport.h | 32 + ports/esp8266/strtoll.c | 29 + ports/esp8266/uart.c | 322 ++++ ports/esp8266/uart.h | 112 ++ ports/esp8266/uart_register.h | 128 ++ ports/esp8266/user_config.h | 1 + ports/esp8266/xtirq.h | 59 + ports/rp2/main.c | 4 +- ports/stm32/Makefile | 11 +- ports/stm32/boardctrl.c | 2 +- ports/stm32/boards/COLUMBUS/manifest.py | 9 +- .../stm32/boards/COLUMBUS/manifest_release.py | 2 + ports/stm32/boards/COLUMBUS/mpconfigboard.h | 11 +- ports/stm32/dma.c | 18 + ports/stm32/eth.h | 4 +- ports/stm32/global.h | 375 +++++ ports/stm32/help.c | 4 +- ports/stm32/make-fs.py | 22 + ports/stm32/mklittlefs.exe | Bin 0 -> 371189 bytes ports/stm32/mklittlefs_linux | Bin 0 -> 100536 bytes ports/stm32/mklittlefs_osx | Bin 0 -> 117848 bytes ports/stm32/modaudio.c | 47 + ports/stm32/mpconfigport.h | 17 + py/builtinhelp.c | 4 +- py/makeversionhdr.py | 6 + shared/runtime/pyexec.c | 4 +- 302 files changed, 13965 insertions(+), 11095 deletions(-) create mode 100644 modules/adaptor/esp32/c3/systemAdaptor.py create mode 100644 modules/adaptor/esp32/m5stack/systemAdaptor.py create mode 100644 modules/adaptor/esp32/nodemcu32s/systemAdaptor.py create mode 100644 modules/adaptor/esp32/s3/systemAdaptor.py create mode 100644 modules/adaptor/stm32/systemAdaptor.py create mode 100644 modules/config.cmake create mode 100644 modules/hmac/component.mk create mode 100644 modules/hmac/include/hmac_sha256.h create mode 100644 modules/hmac/include/sha256.h create mode 100644 modules/hmac/src/hmac_sha256.c create mode 100644 modules/hmac/src/modhmac.c create mode 100644 modules/hmac/src/sha256.c delete mode 100644 modules/modbus/__init__.py create mode 100644 modules/system/component.mk create mode 100644 modules/system/include/system.h delete mode 100644 modules/system/modsystem.c create mode 100644 modules/system/src/modsystem.c rename modules/{ulinksdk/aliyunIoT_test.py => ualiyunIoT/aliyunIoT.py} (65%) create mode 100644 modules/ukv/kv.py rename modules/ulinksdk/{_uhttp => }/__init__.py (100%) delete mode 100644 modules/ulinksdk/_uhttp/client.py delete mode 100644 modules/ulinksdk/_uhttp/example_client.py delete mode 100644 modules/ulinksdk/_umqtt/__init__.py delete mode 100644 modules/ulinksdk/_umqtt/errno.py delete mode 100644 modules/ulinksdk/_umqtt/robust2.py delete mode 100644 modules/ulinksdk/_umqtt/simple2.py delete mode 100644 modules/ulinksdk/crc16.py delete mode 100644 modules/ulinksdk/email/__init__.py delete mode 100644 modules/ulinksdk/email/errors.py delete mode 100644 modules/ulinksdk/email/feedparser.py delete mode 100644 modules/ulinksdk/email/iterators.py delete mode 100644 modules/ulinksdk/email/message.py delete mode 100644 modules/ulinksdk/email/parser.py rename modules/ulinksdk/{upload_file.py => fileupload.py} (68%) delete mode 100644 modules/ulinksdk/hashlib/__init__.py delete mode 100644 modules/ulinksdk/hashlib/_sha224.py delete mode 100644 modules/ulinksdk/hashlib/_sha256.py delete mode 100644 modules/ulinksdk/hashlib/_sha384.py delete mode 100644 modules/ulinksdk/hashlib/_sha512.py delete mode 100644 modules/ulinksdk/hmac.py delete mode 100644 modules/ulinksdk/main.py create mode 100644 modules/ulinksdk/manifest.py delete mode 100644 modules/ulinksdk/urllib/__init__.py delete mode 100644 modules/ulinksdk/urllib/parse.py delete mode 100644 modules/ulinksdk/warnings.py create mode 100644 modules/umqtt.robust/umqtt/robust2.py create mode 100644 modules/umqtt.simple/umqtt/simple2.py create mode 100644 modules/utility/component.mk create mode 100644 modules/utility/include/crc16.h create mode 100644 modules/utility/src/crc16_ibm.c create mode 100644 modules/utility/src/crc16_modbus.c create mode 100644 modules/utility/src/modutility.c create mode 100644 modules/w5500/w5500.py delete mode 100644 ports/esp32/board_config.h create mode 100644 ports/esp32/boards/ESP32_S2_WROVER/board.json create mode 100644 ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.cmake create mode 100644 ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.h create mode 100644 ports/esp32/boards/ESP32_S2_WROVER/sdkconfig.board create mode 100644 ports/esp32/boards/GENERIC/board.json create mode 100644 ports/esp32/boards/GENERIC/board.md create mode 100644 ports/esp32/boards/GENERIC/manifest copy.py create mode 100644 ports/esp32/boards/GENERIC/manifest.py create mode 100644 ports/esp32/boards/GENERIC/manifest_release.py delete mode 100644 ports/esp32/boards/GENERIC/sdkconfig.board create mode 100644 ports/esp32/boards/GENERIC_C3/board.json create mode 100644 ports/esp32/boards/GENERIC_C3/manifest.py create mode 100644 ports/esp32/boards/GENERIC_C3/manifest_release.py delete mode 100644 ports/esp32/boards/GENERIC_C3/sdkconfig.board create mode 100644 ports/esp32/boards/GENERIC_C3_USB/board.json create mode 100644 ports/esp32/boards/GENERIC_D2WD/board.json create mode 100644 ports/esp32/boards/GENERIC_OTA/board.json create mode 100644 ports/esp32/boards/GENERIC_S2/board.json create mode 100644 ports/esp32/boards/GENERIC_S3/board.json create mode 100644 ports/esp32/boards/GENERIC_S3/manifest.py create mode 100644 ports/esp32/boards/GENERIC_S3/manifest_release.py create mode 100644 ports/esp32/boards/GENERIC_S3_SPIRAM/board.json create mode 100644 ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.cmake create mode 100644 ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.h create mode 100644 ports/esp32/boards/GENERIC_S3_SPIRAM/sdkconfig.board rename ports/esp32/boards/{M5STACK_CORE2 => GENERIC_SPIRAM}/board.json (88%) rename ports/esp32/boards/{M5STACK_CORE2 => GENERIC_SPIRAM}/board.md (100%) create mode 100644 ports/esp32/boards/LOLIN_S2_MINI/board.json create mode 100644 ports/esp32/boards/LOLIN_S2_MINI/manifest.py create mode 100644 ports/esp32/boards/LOLIN_S2_MINI/modules/s2mini.py create mode 100644 ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake create mode 100644 ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h create mode 100644 ports/esp32/boards/LOLIN_S2_MINI/sdkconfig.board create mode 100644 ports/esp32/boards/LOLIN_S2_PICO/board.json create mode 100644 ports/esp32/boards/LOLIN_S2_PICO/manifest.py create mode 100644 ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico.py create mode 100644 ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py create mode 100644 ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake create mode 100644 ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h create mode 100644 ports/esp32/boards/LOLIN_S2_PICO/sdkconfig.board create mode 100644 ports/esp32/boards/M5STACK_ATOM/board.json delete mode 100644 ports/esp32/boards/M5STACK_CORE2/mpconfigboard.cmake delete mode 100644 ports/esp32/boards/M5STACK_CORE2/mpconfigboard.h delete mode 100644 ports/esp32/boards/M5STACK_CORE2/sdkconfig.board delete mode 100644 ports/esp32/boards/M5STACK_CORE2/sdkconfig.lvgl create mode 100644 ports/esp32/boards/SIL_WESP32/board.json create mode 100644 ports/esp32/boards/UM_FEATHERS2/board.json create mode 100644 ports/esp32/boards/UM_FEATHERS2/board.md create mode 100644 ports/esp32/boards/UM_FEATHERS2/deploy.md create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/board.json create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/board.md create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/deploy.md create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/manifest.py create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/modules/feathers2neo.py create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h create mode 100644 ports/esp32/boards/UM_FEATHERS2NEO/sdkconfig.board create mode 100644 ports/esp32/boards/UM_TINYPICO/board.json create mode 100644 ports/esp32/boards/UM_TINYPICO/board.md create mode 100644 ports/esp32/boards/UM_TINYPICO/deploy.md create mode 100644 ports/esp32/boards/UM_TINYS2/board.json create mode 100644 ports/esp32/boards/UM_TINYS2/board.md create mode 100644 ports/esp32/boards/UM_TINYS2/deploy.md create mode 100644 ports/esp32/boards/deploy.md create mode 100644 ports/esp32/boards/deploy_s2.md delete mode 100644 ports/esp32/components/dummy_main/CMakeLists.txt delete mode 100644 ports/esp32/components/dummy_main/component.mk delete mode 100644 ports/esp32/components/dummy_main/main_dummy.c delete mode 100644 ports/esp32/font_petme128_8x8.h delete mode 100644 ports/esp32/fs/boot.py delete mode 100644 ports/esp32/fs/lib/appOta.py delete mode 100644 ports/esp32/fs/lib/bleNetConfig.py delete mode 100644 ports/esp32/fs/lib/display_driver.py delete mode 100644 ports/esp32/fs/lib/oneMinuteOnCloud.py delete mode 100644 ports/esp32/libs/neopixel.c delete mode 100644 ports/esp32/libs/neopixel.h delete mode 100644 ports/esp32/m5stackcore2/boot.py delete mode 100644 ports/esp32/m5stackcore2/lib/axp192.py delete mode 100644 ports/esp32/m5stackcore2/lib/i2c_bus.py delete mode 100644 ports/esp32/m5stackcore2/lib/pcm.py delete mode 100644 ports/esp32/m5stackcore2/lib/uai.py delete mode 100644 ports/esp32/machine_adc.h delete mode 100644 ports/esp32/machine_adcblock.c delete mode 100644 ports/esp32/machine_adcblock.h delete mode 100644 ports/esp32/modules/uai.py create mode 100644 ports/esp32/network_wlan.c delete mode 100644 ports/esp32/partitions-16MiB-priv.csv create mode 100644 ports/esp32/partitions-8MiB.csv delete mode 100644 ports/esp32/utility.h create mode 100644 ports/esp8266/Makefile create mode 100644 ports/esp8266/README.md create mode 100644 ports/esp8266/boards/GENERIC/board.json create mode 100644 ports/esp8266/boards/GENERIC/board.md create mode 100644 ports/esp8266/boards/GENERIC/manifest.py create mode 100644 ports/esp8266/boards/GENERIC/mpconfigboard.h create mode 100644 ports/esp8266/boards/GENERIC/mpconfigboard.mk create mode 100644 ports/esp8266/boards/GENERIC_1M/board.json create mode 100644 ports/esp8266/boards/GENERIC_1M/board.md create mode 100644 ports/esp8266/boards/GENERIC_1M/mpconfigboard.h create mode 100644 ports/esp8266/boards/GENERIC_1M/mpconfigboard.mk create mode 100644 ports/esp8266/boards/GENERIC_512K/_boot.py create mode 100644 ports/esp8266/boards/GENERIC_512K/board.json create mode 100644 ports/esp8266/boards/GENERIC_512K/board.md create mode 100644 ports/esp8266/boards/GENERIC_512K/manifest.py create mode 100644 ports/esp8266/boards/GENERIC_512K/mpconfigboard.h create mode 100644 ports/esp8266/boards/GENERIC_512K/mpconfigboard.mk create mode 100644 ports/esp8266/boards/deploy.md create mode 100644 ports/esp8266/boards/eagle.rom.addr.v6.ld create mode 100644 ports/esp8266/boards/esp8266_1m.ld create mode 100644 ports/esp8266/boards/esp8266_2m.ld create mode 100644 ports/esp8266/boards/esp8266_512k.ld create mode 100644 ports/esp8266/boards/esp8266_common.ld create mode 100644 ports/esp8266/boards/esp8266_ota.ld create mode 100644 ports/esp8266/boards/manifest.py create mode 100644 ports/esp8266/esp_init_data.c create mode 100644 ports/esp8266/esp_mphal.c create mode 100644 ports/esp8266/esp_mphal.h create mode 100644 ports/esp8266/espapa102.c create mode 100644 ports/esp8266/espapa102.h create mode 100644 ports/esp8266/esppwm.c create mode 100644 ports/esp8266/esppwm.h create mode 100644 ports/esp8266/ets_alt_task.c create mode 100644 ports/esp8266/ets_alt_task.h create mode 100644 ports/esp8266/etshal.h create mode 100644 ports/esp8266/fatfs_port.c create mode 100644 ports/esp8266/gccollect.c create mode 100644 ports/esp8266/gccollect.h create mode 100644 ports/esp8266/gchelper.s create mode 100644 ports/esp8266/help.c create mode 100644 ports/esp8266/hspi.c create mode 100644 ports/esp8266/hspi.h create mode 100644 ports/esp8266/hspi_register.h create mode 100644 ports/esp8266/lexerstr32.c create mode 100644 ports/esp8266/machine_adc.c create mode 100644 ports/esp8266/machine_bitstream.c create mode 100644 ports/esp8266/machine_hspi.c create mode 100644 ports/esp8266/machine_pin.c create mode 100644 ports/esp8266/machine_pwm.c create mode 100644 ports/esp8266/machine_rtc.c create mode 100644 ports/esp8266/machine_uart.c create mode 100644 ports/esp8266/machine_wdt.c create mode 100644 ports/esp8266/main.c create mode 100644 ports/esp8266/makeimg.py create mode 100644 ports/esp8266/modesp.c create mode 100644 ports/esp8266/modmachine.c create mode 100644 ports/esp8266/modmachine.h create mode 100644 ports/esp8266/modnetwork.c create mode 100644 ports/esp8266/modpyb.c create mode 100644 ports/esp8266/modules/_boot.py create mode 100644 ports/esp8266/modules/apa102.py create mode 100644 ports/esp8266/modules/flashbdev.py create mode 100644 ports/esp8266/modules/inisetup.py rename ports/{esp32 => esp8266}/modules/ntptime.py (100%) create mode 100644 ports/esp8266/modules/port_diag.py create mode 100644 ports/esp8266/moduos.c create mode 100644 ports/esp8266/modutime.c create mode 100644 ports/esp8266/mpconfigport.h rename ports/{esp32/utility.c => esp8266/posix_helpers.c} (55%) create mode 100644 ports/esp8266/qstrdefsport.h create mode 100644 ports/esp8266/strtoll.c create mode 100644 ports/esp8266/uart.c create mode 100644 ports/esp8266/uart.h create mode 100644 ports/esp8266/uart_register.h create mode 100644 ports/esp8266/user_config.h create mode 100644 ports/esp8266/xtirq.h create mode 100644 ports/stm32/global.h create mode 100644 ports/stm32/make-fs.py create mode 100644 ports/stm32/mklittlefs.exe create mode 100755 ports/stm32/mklittlefs_linux create mode 100755 ports/stm32/mklittlefs_osx create mode 100644 ports/stm32/modaudio.c diff --git a/.gitattributes b/.gitattributes index e6d31d6aa3..41856d60ef 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # Per default everything gets normalized and gets LF line endings on checkout. -* text eol=lf +# * text eol=lf # These will always have CRLF line endings on checkout. *.vcxproj text eol=crlf diff --git a/.gitignore b/.gitignore index b4c8128bd5..df8e1b26cf 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ py/__pycache__/* mpy-cross/build/* mpy-cross/mpy-cross.map +mpy-cross/mpy-cross build/* .DS_Store @@ -14,4 +15,6 @@ build/* lib/lv_bindings/pycparser/pycparser/__pycache__ lib/lv_bindings/pycparser/pycparser/ply/__pycache__ -.vscode/* \ No newline at end of file +.vscode/* +modules/ulinksdk_test/* +*.zip diff --git a/README.md b/README.md index d197924f0e..367c145e9b 100644 --- a/README.md +++ b/README.md @@ -1,188 +1,53 @@ -[![CI badge](https://github.com/micropython/micropython/workflows/unix%20port/badge.svg)](https://github.com/micropython/micropython/actions?query=branch%3Amaster+event%3Apush) [![codecov](https://codecov.io/gh/micropython/micropython/branch/master/graph/badge.svg?token=I92PfD05sD)](https://codecov.io/gh/micropython/micropython) -The MicroPython project +HaaS Python 工程 ======================= -

- MicroPython Logo -

- -This is the MicroPython project, which aims to put an implementation -of Python 3.x on microcontrollers and small embedded systems. -You can find the official website at [micropython.org](http://www.micropython.org). - -WARNING: this project is in beta stage and is subject to changes of the -code-base, including project-wide name changes and API changes. - -MicroPython implements the entire Python 3.4 syntax (including exceptions, -`with`, `yield from`, etc., and additionally `async`/`await` keywords from -Python 3.5). The following core datatypes are provided: `str` (including -basic Unicode support), `bytes`, `bytearray`, `tuple`, `list`, `dict`, `set`, -`frozenset`, `array.array`, `collections.namedtuple`, classes and instances. -Builtin modules include `sys`, `time`, and `struct`, etc. Select ports have -support for `_thread` module (multithreading). Note that only a subset of -Python 3 functionality is implemented for the data types and modules. - -MicroPython can execute scripts in textual source form or from precompiled -bytecode, in both cases either from an on-device filesystem or "frozen" into -the MicroPython executable. - -See the repository http://github.com/micropython/pyboard for the MicroPython -board (PyBoard), the officially supported reference electronic circuit board. - -Major components in this repository: -- py/ -- the core Python implementation, including compiler, runtime, and - core library. -- mpy-cross/ -- the MicroPython cross-compiler which is used to turn scripts - into precompiled bytecode. -- ports/unix/ -- a version of MicroPython that runs on Unix. -- ports/stm32/ -- a version of MicroPython that runs on the PyBoard and similar - STM32 boards (using ST's Cube HAL drivers). -- ports/minimal/ -- a minimal MicroPython port. Start with this if you want - to port MicroPython to another microcontroller. -- tests/ -- test framework and test scripts. -- docs/ -- user documentation in Sphinx reStructuredText format. Rendered - HTML documentation is available at http://docs.micropython.org. - -Additional components: -- ports/bare-arm/ -- a bare minimum version of MicroPython for ARM MCUs. Used - mostly to control code size. -- ports/teensy/ -- a version of MicroPython that runs on the Teensy 3.1 - (preliminary but functional). -- ports/pic16bit/ -- a version of MicroPython for 16-bit PIC microcontrollers. -- ports/cc3200/ -- a version of MicroPython that runs on the CC3200 from TI. -- ports/esp8266/ -- a version of MicroPython that runs on Espressif's ESP8266 SoC. -- ports/esp32/ -- a version of MicroPython that runs on Espressif's ESP32 SoC. -- ports/nrf/ -- a version of MicroPython that runs on Nordic's nRF51 and nRF52 MCUs. -- extmod/ -- additional (non-core) modules implemented in C. -- tools/ -- various tools, including the pyboard.py module. -- examples/ -- a few example Python scripts. - -The subdirectories above may include READMEs with additional info. - -"make" is used to build the components, or "gmake" on BSD-based systems. -You will also need bash, gcc, and Python 3.3+ available as the command `python3` -(if your system only has Python 2.7 then invoke make with the additional option -`PYTHON=python2`). - -The MicroPython cross-compiler, mpy-cross ------------------------------------------ - -Most ports require the MicroPython cross-compiler to be built first. This -program, called mpy-cross, is used to pre-compile Python scripts to .mpy -files which can then be included (frozen) into the firmware/executable for -a port. To build mpy-cross use: - - $ cd mpy-cross - $ make - -The Unix version ----------------- - -The "unix" port requires a standard Unix environment with gcc and GNU make. -x86 and x64 architectures are supported (i.e. x86 32- and 64-bit), as well -as ARM and MIPS. Making full-featured port to another architecture requires -writing some assembly code for the exception handling and garbage collection. -Alternatively, fallback implementation based on setjmp/longjmp can be used. - -To build (see section below for required dependencies): - - $ cd ports/unix - $ make submodules - $ make - -Then to give it a try: - - $ ./micropython - >>> list(5 * x + y for x in range(10) for y in [4, 2, 1]) - -Use `CTRL-D` (i.e. EOF) to exit the shell. -Learn about command-line options (in particular, how to increase heap size -which may be needed for larger applications): - $ ./micropython -h +HaaS Python是阿里云IoT团队最新研发的一套低代码编程框架,兼容MicroPython编程规范,依托HaaS平台软硬件积木提供AI、支付、蓝牙配网、云连接、UI等物联网场景常用的能力,从而解决了物联网应用开发难的问题。有了Python轻应用框架,物联网编程不再局限于专业软件开发人员,一般的技术员也可以快速实现复杂的物联网需求。 +更多HaaS Python介绍和开发资料见[HaaS Python官网](https://haas.iot.aliyun.com/haasapi/index.html?spm=a2cpu.b16145223.0.0.595660b1dZDX71#/),[创意案例](https://haas.iot.aliyun.com/solution),[硬件积木](https://haas.iot.aliyun.com/solution/hardware) -Run complete testsuite: +HaaS Python is a set of low-code programming frameworks newly developed by Alibaba Cloud IoT team. It is compatible with MicroPython programming specifications. It relies on the hardware and software building blocks of the HaaS platform to provide capabilities commonly used in IoT scenarios such as AI, payment, Bluetooth network configuration, cloud connection, and UI. Solve the difficult problem of IoT application development. With the Python light application framework, IoT programming is no longer limited to professional software developers, and general technicians can quickly implement complex IoT requirements. +For more HaaS Python introduction and development materials, see [HaaS Python official website](https://haas.iot.aliyun.com/haasapi/index.html?spm=a2cpu.b16145223.0.0.595660b1dZDX71#/), [Creative Case](https://haas.iot.aliyun.com/solution), [Hardware Building Blocks](https://haas.iot.aliyun.com/solution/hardware) - $ make test +该仓库包含的主要组件为: -Unix version comes with a builtin package manager called upip, e.g.: +- py/ -- 核心 Python 实现,包括编译器、运行时和核心库。 +- mpy-cross/ -- 用于将转换脚本预编译为字节码的交叉编译器。 +- external/ -- 扩展三方C组件。 +- modules/ -- 扩展 Python 接口。 +- tests/ -- 测试框架和测试脚本。 +- docs/ -- HaaS Python官方文档及相关案例。 - $ ./micropython -m upip install micropython-pystone - $ ./micropython -m pystone +附加部分: +- ports/esp32/ -- 运行在Espressif的ESP32 SoC上的HaaS Python版本。 +- ports/stm32/ -- 在PyBoard和类似的STM32板上运行的HaaS Python版本(使用ST的Cube HAL驱动程序)。 +- ports/rp2/ -- 运行在Raspberry-Pi-Pico SoC上的HaaS Python版本。 +- ports/haas/ -- 运行在 [AliOS-Things](https://github.com/alibaba/AliOS-Things) 系统, HaaS1000 SoC上的HaaS Python版本。 +- extmod/ -- 用C实现的附加(非核心)模块。 +- tools/ -- 各种工具,包括 pyboard.py 模块。 +- examples/ -- 一些示例 Python 脚本。 -Browse available modules on -[PyPI](https://pypi.python.org/pypi?%3Aaction=search&term=micropython). -Standard library modules come from -[micropython-lib](https://github.com/micropython/micropython-lib) project. - -External dependencies ---------------------- - -Building MicroPython ports may require some dependencies installed. - -For Unix port, `libffi` library and `pkg-config` tool are required. On -Debian/Ubuntu/Mint derivative Linux distros, install `build-essential` -(includes toolchain and make), `libffi-dev`, and `pkg-config` packages. - -Other dependencies can be built together with MicroPython. This may -be required to enable extra features or capabilities, and in recent -versions of MicroPython, these may be enabled by default. To build -these additional dependencies, in the port directory you're -interested in (e.g. `ports/unix/`) first execute: - - $ make submodules +HaaS Python 交叉编译器 mpy-cross +----------------------------------------- -This will fetch all the relevant git submodules (sub repositories) that -the port needs. Use the same command to get the latest versions of -submodules as they are updated from time to time. After that execute: +大多数端口都需要先构建 HaaS Python 交叉编译器。这个名为 mpy-cross 的程序用于将 Python 脚本预编译为 .mpy 文件,然后可以将这些文件包含(冻结)到固件/可执行文件的端口中。 - $ make deplibs -This will build all available dependencies (regardless whether they -are used or not). If you intend to build MicroPython with additional -options (like cross-compiling), the same set of options should be passed -to `make deplibs`. To actually enable/disable use of dependencies, edit -`ports/unix/mpconfigport.mk` file, which has inline descriptions of the options. -For example, to build SSL module (required for `upip` tool described above, -and so enabled by default), `MICROPY_PY_USSL` should be set to 1. +构建 mpy-cross 使用: -For some ports, building required dependences is transparent, and happens -automatically. But they still need to be fetched with the `make submodules` -command. + $ cd mpy-cross + $ make The STM32 version ----------------- -The "stm32" port requires an ARM compiler, arm-none-eabi-gcc, and associated -bin-utils. For those using Arch Linux, you need arm-none-eabi-binutils, -arm-none-eabi-gcc and arm-none-eabi-newlib packages. Otherwise, try here: -https://launchpad.net/gcc-arm-embedded - -To build: +“stm32” 端口需要 ARM 编译器 arm-none-eabi-gcc 和相关的 bin-utils。 +使用下面的命令编译: $ cd ports/stm32 - $ make submodules $ make -You then need to get your board into DFU mode. On the pyboard, connect the -3V3 pin to the P1/DFU pin with a wire (on PYBv1.0 they are next to each other -on the bottom left of the board, second row from the bottom). - -Then to flash the code via USB DFU to your device: - - $ make deploy - -This will use the included `tools/pydfu.py` script. If flashing the firmware -does not work it may be because you don't have the correct permissions, and -need to use `sudo make deploy`. -See the README.md file in the ports/stm32/ directory for further details. - Contributing ------------ -MicroPython is an open-source project and welcomes contributions. To be -productive, please be sure to follow the -[Contributors' Guidelines](https://github.com/micropython/micropython/wiki/ContributorGuidelines) -and the [Code Conventions](https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md). -Note that MicroPython is licenced under the MIT license, and all contributions -should follow this license. +HaaS Python 是一个开源项目,欢迎贡献。 +HaaS Python 在 MIT 许可下获得许可,所有贡献都应遵循此许可。 diff --git a/external/activation/include/activation.h b/external/activation/include/activation.h index 4e81b8ed38..4626c51dbb 100644 --- a/external/activation/include/activation.h +++ b/external/activation/include/activation.h @@ -46,7 +46,7 @@ Connection: Keep-Alive\r\n\r\n%s\r\n" #define ACTIVATION_AOS_VERSION_PREFIX "AOS-R-" #define ACTIVATION_AOS_VERSION_POSTFIX_MAX_LEN (8) -#define MAX_WAIT_MS (12 * 1000) +#define MAX_WAIT_MS (5 * 1000) #define DELAY_PERIOD_MS (100) /* tigger activation report */ diff --git a/external/activation/src/activation.c b/external/activation/src/activation.c index 6172e8c683..d0d87ff7fa 100644 --- a/external/activation/src/activation.c +++ b/external/activation/src/activation.c @@ -180,7 +180,7 @@ int32_t activation_parse_data(char *response_data) char *result_end; int32_t len; - ACTIVATION_DEBUG("response data: %s\n", response_data); + ACTIVATION_ERR("response data: %s\n", response_data); result_start = strstr(response_data, ACTIVATION_RESPONSE_RESULT_START); result_end = strstr(result_start, ACTIVATION_RESPONSE_RESULT_END); if ((result_start != NULL) && (result_end != NULL)) { diff --git a/external/external.mk b/external/external.mk index 313790cc94..4a60a5aef3 100644 --- a/external/external.mk +++ b/external/external.mk @@ -41,8 +41,4 @@ ifeq ($(AOS_AMP_TASK), 1) include $(EXTERNAL_DIR)/amp_task/amp_task.mk endif -# SRC_HAAS += $(foreach dir, $(SRCDIRS_HAAS), $(wildcard $(dir)/*.c)) -# $(info 'SRC_HAAS = $(SRC_HAAS)') - INC += $(addprefix -I, ${INC_HAAS}) -# $(info 'INC = $(INC)') diff --git a/lib/lv_bindings/lvgl/examples/libs/freetype/arial.ttf b/lib/lv_bindings/lvgl/examples/libs/freetype/arial.ttf index e479e4ce4a7356411b02a1c4ae8e2016e84847ed..a9f73f957794d5645af92602ff1b5e97042c4e75 100644 GIT binary patch delta 30 jcmcc9D15t7ctgy#=GblRvD+Admz7CzZp!*4m}H7+W|(D8Tfe;dQQNzzxA#K# zs+?VsC6=|bt6cWVa4W2`#yT6?aGPsKZR{4??6Auo`y6n{5yzZx${FWe(4yc{Z|s#j F`vWYpQM&*D delta 235 zcmWN{DGtI=07cQ1c`Wl-=6PzNV8D@d8EgQ-0>B}DfV{37rOI;IjoIc5+cgb_g$F~p@~s)sv`@l%mN5-FsSK~@^ST>f>k z?7Xz>Lia|!-4szmS#CGlb=1Pa@|A=ZFJB@4}A> 12 else: raise Exception('readVoltage disabled when ADC closed') diff --git a/modules/driver/gpio.py b/modules/driver/gpio.py index 9c31734725..a9cc450dd6 100644 --- a/modules/driver/gpio.py +++ b/modules/driver/gpio.py @@ -3,6 +3,7 @@ import sys from boardparser import BoardConfigParser +import systemAdaptor from machine import Pin as mach_Pin BOARD_JSON_PATH = '/data/pyamp/board.json' @@ -18,15 +19,6 @@ """ class GPIO: - _dirStrToInt = { - 'input': mach_Pin.IN, # 0 - 'output': mach_Pin.OUT, # 1 - 'alt': mach_Pin.ALT, # 2 - 'analog': mach_Pin.ANALOG, # 3 - 'opendrain': mach_Pin.OUT_OD, # 17 - 'altopendrain': mach_Pin.ALT_OPEN_DRAIN # 18 - } - def __init__(self): self.pin = None @@ -34,7 +26,8 @@ def open(self, node): if self.pin is not None: return -1 - if type(node) is str: + if type(node) is str: + pinDict = systemAdaptor.getSupportedPinMode() parser = BoardConfigParser() try: item = parser.findItem(node, 'GPIO') @@ -42,17 +35,14 @@ def open(self, node): print(e) return BoardConfigParser.NODE_NOT_EXIST - if type(item['port']) is not str: - raise ValueError('port fild should be str') - else: - self.port = item['port'] + self.port = item['port'] # Check dir option if 'dir' in item: - self.dir = self._dirStrToInt[item['dir']] + self.dir = pinDict[item['dir']] else: raise Exception('dir un-assigned') - + del pinDict # Check pull option if 'pull' in item: if item['pull'] == 'pullup': @@ -60,11 +50,11 @@ def open(self, node): elif item['pull'] == 'pulldown': self.pull = mach_Pin.PULL_DOWN elif item['pull'] == 'none': - self.pull = mach_Pin.PULL_NONE + self.pull = None else: raise ValueError('unSupported pull type, avaiable type is {pullup, pulldown, none}') else: - self.pull = mach_Pin.PULL_NONE + self.pull = None # Check intMode exist or not if('intMode' in item): @@ -82,9 +72,7 @@ def open(self, node): self.pin = mach_Pin(self.port, mode=self.dir, pull=self.pull, - value=None, - af=-1, - alt=-1) + value=None) return 0 else: raise ValueError('Node type should be str') diff --git a/modules/driver/i2c.py b/modules/driver/i2c.py index 4f4d5292ea..57cb3ee502 100644 --- a/modules/driver/i2c.py +++ b/modules/driver/i2c.py @@ -1,10 +1,9 @@ # Adapter for machine driver -import sys - from boardparser import BoardConfigParser from machine import I2C as mach_I2C from machine import Pin as mach_Pin +import systemAdaptor """ @@ -46,13 +45,18 @@ def open(self, node): self.mode = item['mode'] self.devAddr = item['devAddr'] - if('scl' in item): + pinMap = systemAdaptor.getPinMap() + i2cName = "I2C" + str(self.port) + if 'scl' in item and 'sda' in item: self.scl = item['scl'] - - if('sda' in item): - self.sda = item['sda'] - - self.i2c = mach_I2C(self.port, freq=self.freq) + self.sda = item['sda'] + self.i2c = mach_I2C(self.port, mach_Pin(self.scl), mach_Pin(self.sda), freq=self.freq) + elif i2cName in pinMap: + self.i2c = mach_I2C(self.port, mach_Pin(pinMap[i2cName]["SCL"]),mach_Pin(pinMap[i2cName]["SDA"]), freq=self.freq) + else: + self.i2c = mach_I2C(self.port, freq=self.freq) + + del pinMap return 0 else: raise ValueError('Node type should be str') diff --git a/modules/driver/spi.py b/modules/driver/spi.py index 2577c4eb0f..1fe7a2cd34 100644 --- a/modules/driver/spi.py +++ b/modules/driver/spi.py @@ -1,17 +1,16 @@ # Adapter for machine driver -import sys - from boardparser import BoardConfigParser from machine import SPI as mach_SPI from machine import Pin as mach_Pin +import systemAdaptor """ SPI APIs: "spi_bmp280": { "type": "SPI", - "port": 3, + "port": 2, "mode": "master", "freq": 2000000 } @@ -40,22 +39,28 @@ def open(self, node): self.port = item['port'] self.freq = item['freq'] self.mode = item['mode'] + self.spi = mach_SPI(self.port, baudrate=self.freq) - if 'sck' in item: + pinMap = systemAdaptor.getPinMap() + spiName = "SPI" + str(self.port) + + if 'sck' in item and 'mosi' in item and 'miso' in item: self.sck = item['sck'] - - if 'mosi' in item: self.mosi = item['mosi'] - - if 'miso' in item: self.miso = item['miso'] - + self.spi = self.spi.init(self.port, baudrate=self.freq, sck=mach_Pin(self.sck), mosi=mach_Pin(self.mosi), miso = mach_Pin(self.miso)) + elif spiName in pinMap: + self.sck = mach_Pin(pinMap[spiName]["SCLK"]) + self.mosi = mach_Pin(pinMap[spiName]["MOSI"]) + self.miso = mach_Pin(pinMap[spiName]["MOSO"]) + self.cs = mach_Pin(pinMap[spiName]["CS"]) + self.spi = self.spi.init(self.port, baudrate=self.freq, sck=mach_Pin(self.sck), mosi=mach_Pin(self.mosi), miso = mach_Pin(self.miso)) + del pinMap + if 'cs' in item: self.cs = item['cs'] - - self.spi = mach_SPI(self.port, baudrate=self.freq) self.cs_pin = mach_Pin(self.cs, mach_Pin.OUT) - + return 0 else: raise ValueError('Node type should be str') @@ -69,16 +74,22 @@ def close(self): def read(self, buf): spi = self.spi - if spi is not None: + cs_pin = self.cs_pin + if spi is not None and cs_pin is not None: + cs_pin.low() spi.readinto(buf) + cs_pin.high() return len(buf) else: return -1 def write(self, buf): spi = self.spi - if spi is not None: + cs_pin = self.cs_pin + if spi is not None and cs_pin is not None: + cs_pin.low() spi.write(buf) + cs_pin.high() return len(buf) else: return -1 diff --git a/modules/driver/uart.py b/modules/driver/uart.py index 8e9c346761..d7af985ea4 100644 --- a/modules/driver/uart.py +++ b/modules/driver/uart.py @@ -1,11 +1,11 @@ # Adapter for machine driver -import sys - from boardparser import BoardConfigParser from machine import UART as mach_UART from machine import Pin as mach_Pin +import systemAdaptor + """ "uart_test": { @@ -67,18 +67,36 @@ def open(self, node): else: raise ValueError('parity:{} mode not supported'.format(item['parity'])) - if 'tx' in item: - self.tx = item['tx'] - - if 'rx' in item: - self.rx = item['rx'] - self.uart = mach_UART(self.port) - self.uart.init(self.baudRate, - bits = self.dataWidth, - parity = self.parity, - stop = self.stopBits, - flow = self.flowControl) + + pinMap = systemAdaptor.getPinMap() + uartName = "UART" + str(self.port) + + if 'tx' in item and 'rx' in item: + self.uart.init(self.baudRate, + bits = self.dataWidth, + parity = self.parity, + stop = self.stopBits, + flow = self.flowControl, + tx = mach_Pin(item['tx']), + rx = mach_Pin(item['rx'])) + + elif uartName in pinMap: + self.uart.init(self.baudRate, + bits = self.dataWidth, + parity = self.parity, + stop = self.stopBits, + flow = self.flowControl, + tx = mach_Pin(pinMap[uartName]["TX"]), + rx = mach_Pin(pinMap[uartName]["RX"])) + else: + self.uart.init(self.baudRate, + bits = self.dataWidth, + parity = self.parity, + stop = self.stopBits, + flow = self.flowControl) + + del pinMap return 0 else: raise ValueError('Node type should be str') diff --git a/modules/driver/wdt.py b/modules/driver/wdt.py index 697fba1cce..6fca4929db 100644 --- a/modules/driver/wdt.py +++ b/modules/driver/wdt.py @@ -1,7 +1,5 @@ # Adapter for machine driver -import sys - from boardparser import BoardConfigParser from machine import WDT as mach_WDT diff --git a/modules/hmac/component.mk b/modules/hmac/component.mk new file mode 100644 index 0000000000..fb09262afa --- /dev/null +++ b/modules/hmac/component.mk @@ -0,0 +1,17 @@ +# +# Component Makefile +# +COMPONENT_DIR = hmac +COMPONENT_SUBMODULES += $(COMPONENT_DIR) + +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := src + +INC_HAAS += $(addprefix $(MODULES_DIR)/$(COMPONENT_DIR)/, $(COMPONENT_ADD_INCLUDEDIRS)) +SRC_HAAS += $(addprefix modules/$(COMPONENT_DIR)/$(COMPONENT_SRCDIRS)/,\ + modhmac.c \ + hmac_sha256.c \ + sha256.c \ +) + +CFLAGS += -DMICROPY_PY_HMAC=1 \ No newline at end of file diff --git a/modules/hmac/include/hmac_sha256.h b/modules/hmac/include/hmac_sha256.h new file mode 100644 index 0000000000..23f86985fe --- /dev/null +++ b/modules/hmac/include/hmac_sha256.h @@ -0,0 +1,35 @@ +/* + Originally written by https://github.com/h5p9sl +*/ + +#ifndef _HMAC_SHA256_H_ +#define _HMAC_SHA256_H_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#include + +size_t // Returns the number of bytes written to `out` +hmac_sha256( + // [in]: The key and its length. + // Should be at least 32 bytes long for optimal security. + const void* key, + const size_t keylen, + + // [in]: The data to hash alongside the key. + const void* data, + const size_t datalen, + + // [out]: The output hash. + // Should be 32 bytes long. If it's less than 32 bytes, + // the resulting hash will be truncated to the specified length. + void* out, + const size_t outlen); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // _HMAC_SHA256_H_ \ No newline at end of file diff --git a/modules/hmac/include/sha256.h b/modules/hmac/include/sha256.h new file mode 100644 index 0000000000..6104364e96 --- /dev/null +++ b/modules/hmac/include/sha256.h @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Sha256 +// +// Implementation of SHA256 hash function. +// Original author: Tom St Denis, tomstdenis@gmail.com, http://libtom.org +// Modified by WaterJuice retaining Public Domain license. +// +// This is free and unencumbered software released into the public domain - +// June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +typedef struct { + uint64_t length; + uint32_t state[8]; + uint32_t curlen; + uint8_t buf[64]; +} Sha256Context; + +#define SHA256_HASH_SIZE (256 / 8) + +typedef struct { + uint8_t bytes[SHA256_HASH_SIZE]; +} SHA256_HASH; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Initialise +// +// Initialises a SHA256 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Initialise(Sha256Context* Context // [out] +); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Update +// +// Adds data to the SHA256 context. This will process the data and update the +// internal state of the context. Keep on calling this function until all the +// data has been added. Then call Sha256Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Update(Sha256Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] +); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Finalise +// +// Performs the final calculation of the hash and returns the digest (32 byte +// buffer containing 256bit hash). After calling this, Sha256Initialised must +// be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Finalise(Sha256Context* Context, // [in out] + SHA256_HASH* Digest // [out] +); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Calculate +// +// Combines Sha256Initialise, Sha256Update, and Sha256Finalise into one +// function. Calculates the SHA256 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Calculate(void const* Buffer, // [in] + uint32_t BufferSize, // [in] + SHA256_HASH* Digest // [in] +); \ No newline at end of file diff --git a/modules/hmac/src/hmac_sha256.c b/modules/hmac/src/hmac_sha256.c new file mode 100644 index 0000000000..0a79f61d61 --- /dev/null +++ b/modules/hmac/src/hmac_sha256.c @@ -0,0 +1,87 @@ +/* + hmac_sha256.c + Originally written by https://github.com/h5p9sl + */ + +#include +#include + +#include "sha256.h" +#include "hmac_sha256.h" +#include "py/runtime.h" + +#define SHA256_BLOCK_SIZE 64 + +/* LOCAL FUNCTIONS */ + +// Concatenate X & Y, return hash. +static void* H(const void* x, const size_t xlen, const void* y, const size_t ylen, void* out, const size_t outlen); + +// Wrapper for sha256 +static void* sha256(const void* data, const size_t datalen, void* out, const size_t outlen); + +size_t hmac_sha256(const void* key, const size_t keylen, const void* data, const size_t datalen, void* out, + const size_t outlen) +{ + uint8_t k[SHA256_BLOCK_SIZE]; + uint8_t k_ipad[SHA256_BLOCK_SIZE]; + uint8_t k_opad[SHA256_BLOCK_SIZE]; + uint8_t ihash[SHA256_HASH_SIZE]; + uint8_t ohash[SHA256_HASH_SIZE]; + size_t sz; + int i; + + memset(k, 0, sizeof(k)); + memset(k_ipad, 0x36, SHA256_BLOCK_SIZE); + memset(k_opad, 0x5c, SHA256_BLOCK_SIZE); + + if (keylen > SHA256_BLOCK_SIZE) { + // If the key is larger than the hash algorithm's + // block size, we must digest it first. + sha256(key, keylen, k, sizeof(k)); + } else { + memcpy(k, key, keylen); + } + + for (i = 0; i < SHA256_BLOCK_SIZE; i++) { + k_ipad[i] ^= k[i]; + k_opad[i] ^= k[i]; + } + + // Perform HMAC algorithm: ( https://tools.ietf.org/html/rfc2104 ) + // `H(K XOR opad, H(K XOR ipad, data))` + H(k_ipad, sizeof(k_ipad), data, datalen, ihash, sizeof(ihash)); + H(k_opad, sizeof(k_opad), ihash, sizeof(ihash), ohash, sizeof(ohash)); + + sz = (outlen > SHA256_HASH_SIZE) ? SHA256_HASH_SIZE : outlen; + memcpy(out, ohash, sz); + return sz; +} + +static void* H(const void* x, const size_t xlen, const void* y, const size_t ylen, void* out, const size_t outlen) +{ + void* result; + size_t buflen = (xlen + ylen); + uint8_t* buf = (uint8_t*)m_malloc(buflen); + + memcpy(buf, x, xlen); + memcpy(buf + xlen, y, ylen); + result = sha256(buf, buflen, out, outlen); + + m_free(buf); + return result; +} + +static void* sha256(const void* data, const size_t datalen, void* out, const size_t outlen) +{ + size_t sz; + Sha256Context ctx; + SHA256_HASH hash; + + Sha256Initialise(&ctx); + Sha256Update(&ctx, data, datalen); + Sha256Finalise(&ctx, &hash); + + sz = (outlen > SHA256_HASH_SIZE) ? SHA256_HASH_SIZE : outlen; + return memcpy(out, hash.bytes, sz); +} \ No newline at end of file diff --git a/modules/hmac/src/modhmac.c b/modules/hmac/src/modhmac.c new file mode 100644 index 0000000000..9701e43b2c --- /dev/null +++ b/modules/hmac/src/modhmac.c @@ -0,0 +1,49 @@ +#include "crc16.h" +#include "hmac_sha256.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/stackctrl.h" +#include "sha256.h" + +#if MICROPY_PY_HMAC + +STATIC mp_obj_t mod_hmac_sha256(mp_obj_t key_in, mp_obj_t data_in) +{ + if (!mp_obj_is_str(key_in)) { + mp_raise_TypeError(MP_ERROR_TEXT("key should be string")); + } + + if (!mp_obj_is_str(data_in)) { + mp_raise_TypeError(MP_ERROR_TEXT("data should be string")); + } + + const char *key = mp_obj_str_get_str(key_in); + const char *data = mp_obj_str_get_str(data_in); + + uint8_t tmp[SHA256_HASH_SIZE] = { 0 }; + size_t len = hmac_sha256(key, strlen(key), data, strlen(data), &tmp, sizeof(tmp)); + + char out_str[SHA256_HASH_SIZE * 2 + 1] = { 0 }; + for (int i = 0; i < len; i++) { + snprintf(&out_str[i * 2], 3, "%02x", tmp[i]); + } + + return mp_obj_new_str((const char *)out_str, len * 2); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_hmac_sha256_obj, mod_hmac_sha256); + +STATIC const mp_rom_map_elem_t mp_module_hmac_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_hmac) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sha256), MP_ROM_PTR(&mod_hmac_sha256_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_hmac_globals, mp_module_hmac_globals_table); + +const mp_obj_module_t mp_module_hmac = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_hmac_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_hmac, mp_module_hmac, MICROPY_PY_HMAC); + +#endif // MICROPY_PY_HMAC \ No newline at end of file diff --git a/modules/hmac/src/sha256.c b/modules/hmac/src/sha256.c new file mode 100644 index 0000000000..7eaacb25bf --- /dev/null +++ b/modules/hmac/src/sha256.c @@ -0,0 +1,269 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Sha256 +// +// Implementation of SHA256 hash function. +// Original author: Tom St Denis, tomstdenis@gmail.com, http://libtom.org +// Modified by WaterJuice retaining Public Domain license. +// +// This is free and unencumbered software released into the public domain - +// June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "sha256.h" + +// #include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MACROS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#define ror(value, bits) (((value) >> (bits)) | ((value) << (32 - (bits)))) + +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +#define STORE32H(x, y) \ + { \ + (y)[0] = (uint8_t)(((x) >> 24) & 255); \ + (y)[1] = (uint8_t)(((x) >> 16) & 255); \ + (y)[2] = (uint8_t)(((x) >> 8) & 255); \ + (y)[3] = (uint8_t)((x)&255); \ + } + +#define LOAD32H(x, y) \ + { \ + x = ((uint32_t)((y)[0] & 255) << 24) | ((uint32_t)((y)[1] & 255) << 16) | ((uint32_t)((y)[2] & 255) << 8) | \ + ((uint32_t)((y)[3] & 255)); \ + } + +#define STORE64H(x, y) \ + { \ + (y)[0] = (uint8_t)(((x) >> 56) & 255); \ + (y)[1] = (uint8_t)(((x) >> 48) & 255); \ + (y)[2] = (uint8_t)(((x) >> 40) & 255); \ + (y)[3] = (uint8_t)(((x) >> 32) & 255); \ + (y)[4] = (uint8_t)(((x) >> 24) & 255); \ + (y)[5] = (uint8_t)(((x) >> 16) & 255); \ + (y)[6] = (uint8_t)(((x) >> 8) & 255); \ + (y)[7] = (uint8_t)((x)&255); \ + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CONSTANTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// The K array +static const uint32_t K[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +#define BLOCK_SIZE 64 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Various logical functions +#define Ch(x, y, z) (z ^ (x & (y ^ z))) +#define Maj(x, y, z) (((x | y) & z) | (x & y)) +#define S(x, n) ror((x), (n)) +#define R(x, n) (((x)&0xFFFFFFFFUL) >> (n)) +#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) +#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) +#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) +#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + +#define Sha256Round(a, b, c, d, e, f, g, h, i) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TransformFunction +// +// Compress 512-bits +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static void TransformFunction(Sha256Context* Context, uint8_t const* Buffer) +{ + uint32_t S[8]; + uint32_t W[64]; + uint32_t t0; + uint32_t t1; + uint32_t t; + int i; + + // Copy state into S + for (i = 0; i < 8; i++) { + S[i] = Context->state[i]; + } + + // Copy the state into 512-bits into W[0..15] + for (i = 0; i < 16; i++) { + LOAD32H(W[i], Buffer + (4 * i)); + } + + // Fill W[16..63] + for (i = 16; i < 64; i++) { + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } + + // Compress + for (i = 0; i < 64; i++) { + Sha256Round(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i); + t = S[7]; + S[7] = S[6]; + S[6] = S[5]; + S[5] = S[4]; + S[4] = S[3]; + S[3] = S[2]; + S[2] = S[1]; + S[1] = S[0]; + S[0] = t; + } + + // Feedback + for (i = 0; i < 8; i++) { + Context->state[i] = Context->state[i] + S[i]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Initialise +// +// Initialises a SHA256 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Initialise(Sha256Context* Context // [out] +) +{ + Context->curlen = 0; + Context->length = 0; + Context->state[0] = 0x6A09E667UL; + Context->state[1] = 0xBB67AE85UL; + Context->state[2] = 0x3C6EF372UL; + Context->state[3] = 0xA54FF53AUL; + Context->state[4] = 0x510E527FUL; + Context->state[5] = 0x9B05688CUL; + Context->state[6] = 0x1F83D9ABUL; + Context->state[7] = 0x5BE0CD19UL; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Update +// +// Adds data to the SHA256 context. This will process the data and update the +// internal state of the context. Keep on calling this function until all the +// data has been added. Then call Sha256Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Update(Sha256Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] +) +{ + uint32_t n; + + if (Context->curlen > sizeof(Context->buf)) { + return; + } + + while (BufferSize > 0) { + if (Context->curlen == 0 && BufferSize >= BLOCK_SIZE) { + TransformFunction(Context, (uint8_t*)Buffer); + Context->length += BLOCK_SIZE * 8; + Buffer = (uint8_t*)Buffer + BLOCK_SIZE; + BufferSize -= BLOCK_SIZE; + } else { + n = MIN(BufferSize, (BLOCK_SIZE - Context->curlen)); + memcpy(Context->buf + Context->curlen, Buffer, (size_t)n); + Context->curlen += n; + Buffer = (uint8_t*)Buffer + n; + BufferSize -= n; + if (Context->curlen == BLOCK_SIZE) { + TransformFunction(Context, Context->buf); + Context->length += 8 * BLOCK_SIZE; + Context->curlen = 0; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Finalise +// +// Performs the final calculation of the hash and returns the digest (32 byte +// buffer containing 256bit hash). After calling this, Sha256Initialised must +// be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Finalise(Sha256Context* Context, // [in out] + SHA256_HASH* Digest // [out] +) +{ + int i; + + if (Context->curlen >= sizeof(Context->buf)) { + return; + } + + // Increase the length of the message + Context->length += Context->curlen * 8; + + // Append the '1' bit + Context->buf[Context->curlen++] = (uint8_t)0x80; + + // if the length is currently above 56 bytes we append zeros + // then compress. Then we can fall back to padding zeros and length + // encoding like normal. + if (Context->curlen > 56) { + while (Context->curlen < 64) { + Context->buf[Context->curlen++] = (uint8_t)0; + } + TransformFunction(Context, Context->buf); + Context->curlen = 0; + } + + // Pad up to 56 bytes of zeroes + while (Context->curlen < 56) { + Context->buf[Context->curlen++] = (uint8_t)0; + } + + // Store length + STORE64H(Context->length, Context->buf + 56); + TransformFunction(Context, Context->buf); + + // Copy output + for (i = 0; i < 8; i++) { + STORE32H(Context->state[i], Digest->bytes + (4 * i)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Calculate +// +// Combines Sha256Initialise, Sha256Update, and Sha256Finalise into one +// function. Calculates the SHA256 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void Sha256Calculate(void const* Buffer, // [in] + uint32_t BufferSize, // [in] + SHA256_HASH* Digest // [in] +) +{ + Sha256Context context; + + Sha256Initialise(&context); + Sha256Update(&context, Buffer, BufferSize); + Sha256Finalise(&context, Digest); +} \ No newline at end of file diff --git a/modules/modbus/__init__.py b/modules/modbus/__init__.py deleted file mode 100644 index 2be784afde..0000000000 --- a/modules/modbus/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .uModBus import uModbus \ No newline at end of file diff --git a/modules/modbus/manifest.py b/modules/modbus/manifest.py index 76d4fc37f4..942eb3c359 100644 --- a/modules/modbus/manifest.py +++ b/modules/modbus/manifest.py @@ -1,11 +1,10 @@ freeze( "..", ( - "modbus/__init__.py", - "modbus/uModBus.py", - "modbus/uModBusConst.py", - "modbus/uModBusFunctions.py", - "modbus/uModBusSerial.py", - "modbus/uModBusTCP.py" + "modbus/uModbus.py", + "modbus/uModbusConst.py", + "modbus/uModbusFunctions.py", + "modbus/uModbusSerial.py", + "modbus/uModbusTCP.py" ), ) \ No newline at end of file diff --git a/modules/modbus/uModBus.py b/modules/modbus/uModBus.py index 8cae79d18c..5b23f30384 100644 --- a/modules/modbus/uModBus.py +++ b/modules/modbus/uModBus.py @@ -1,8 +1,7 @@ +from modbus.uModbusSerial import uModbusSerial +from modbus.uModbusTCP import uModbusTCP from boardparser import BoardConfigParser -from modbus.uModBusSerial import uModBusSerial -from modbus.uModBusTCP import uModBusTCP - -BOARD_JSON_PATH = '/data/pyamp/board.json' +import modbus.uModbusConst as Const """ "modbus_test": { @@ -66,7 +65,7 @@ def open(self, node): elif priority == 'odd': self.priority = self.PARITY_ODD else: - raise ValueError('unSupported priority: {}, valid choice: ["none", "tcp", "serial"]'.format(item['priority'])) + raise ValueError('unSupported priority: {}, valid choice: ["none", "even", "odd"]'.format(item['priority'])) if 'stopBits' in item: self.stopBits = item['stopBits'] @@ -106,10 +105,8 @@ def open(self, node): self.ctrl_pin = item['ctrl_pin'] else: self.ctrl_pin = None - - print('pins = ', self.serial_pins) - self.modbus = uModBusSerial(self.port, + self.modbus = uModbusSerial(self.port, baudrate = self.baudRate, data_bits = self.dataWidth, stop_bits = self.stopBits, @@ -127,40 +124,127 @@ def open(self, node): if('timeout' in item): self.timeout = item['timeout'] - self.modbus = uModBusTCP(self.ip_addr, self.ip_port, self.timeout) + self.modbus = uModbusTCP(self.ip_addr, self.ip_port, self.timeout) else: raise ValueError('unSupported mode: {}, valid choice: ["tcp", "serial"]'.format(item['mode'])) else: raise ValueError('Node type should be str') def close(self): - self.modbus.close() + if self.modbus is not None: + self.modbus.close() + self.modbus = None + else: + raise OSError('modbus not opened....') + return 0 def readCoils(self, slave_addr, starting_addr, reg_quantity, data): ret = self.modbus.read_coils(slave_addr, starting_addr, reg_quantity) - return bytearray(ret) - def readDiscreteInputs(self, slave_addr, starting_address, reg_quantity, data): + for i in range(reg_quantity): + data[i] = ret[i] + + return reg_quantity + + def readDiscreteInputs(self, slave_addr, starting_addr, reg_quantity, data): ret = self.modbus.read_discrete_inputs(slave_addr, starting_addr, reg_quantity) - return bytearray(ret) - def readHoldingRegisters(self, slave_addr, starting_address, reg_quantity, data): + for i in range(reg_quantity): + data[i] = ret[i] + + return reg_quantity + + def readHoldingRegisters(self, slave_addr, starting_addr, reg_quantity, data): ret = self.modbus.read_holding_registers(slave_addr, starting_addr, reg_quantity, signed=True) - return bytearray(ret) - def readInputRegisters(self, slave_addr, starting_address, reg_quantity, data): - ret = self.modbus.read_input_registers(slave_addr, starting_address, reg_quantity, signed=True) - return bytearray(ret) + for i in range(reg_quantity): + data[i] = ret[i] + + return reg_quantity + + def readInputRegisters(self, slave_addr, starting_addr, reg_quantity, data): + ret = self.modbus.read_input_registers(slave_addr, starting_addr, reg_quantity, signed=True) + + for i in range(reg_quantity): + data[i] = ret[i] + + return reg_quantity def writeSingleCoil(self, slave_addr, coil_addr, coil_value): return self.modbus.write_single_coil(slave_addr, coil_addr, coil_value) - def writeSingleRegister(self, slave_addr, register_addr, register_value): - return self.modbus.write_single_register(slave_addr, register_addr, register_value, signed=True) - - def writeMultipleCoils(self, slave_addr, starting_address, reg_quantity, data): - return self.modbus.write_multiple_coils(slave_addr, starting_address, data) + def writeSingleRegister(self, slave_addr, reg_addr, reg_value): + return self.modbus.write_single_register(slave_addr, reg_addr, reg_value, signed=True) + + def writeMultipleCoils(self, slave_addr, starting_addr, reg_quantity, data): + return self.modbus.write_multiple_coils(slave_addr, starting_addr, data) + + def writeMultipleRegisters(self, slave_addr, starting_addr, reg_quantity, data): + return self.modbus.write_multiple_registers(slave_addr, starting_addr, data, signed=True) + + def writeRaw(self, data): + slave_addr = data[0] + fun_code = data[1] + start_addr = data[2] * 256 + data[3] + value_qty = data[4] * 256 + data[5] + + if fun_code == Const.WRITE_SINGLE_COIL: + return self.writeSingleCoil(slave_addr, start_addr, value_qty) + + elif fun_code == Const.WRITE_SINGLE_REGISTER: + reg_value = data[4] * 256 + data[5] + return self.writeSingleRegister(slave_addr, start_addr, value_qty) + + elif fun_code == Const.WRITE_MULTIPLE_COILS: + count = data[6] + out = bytearray(value_qty) + index = 0 + for i in range(count): + for k in range(8): + out[index] = (data[7+i] >> k) & 0x01 + if index == value_qty - 1: + break + else: + index += 1 + + return self.writeMultipleCoils(slave_addr, start_addr, value_qty, out) + + elif fun_code == Const.WRITE_MULTIPLE_REGISTERS: + count = int(data[6] / 2) + out = bytearray(count) + for i in range(count): + out[i] = data[7+i*2] * 256 + data[7+i*2+1] + + return self.writeMultipleRegisters(slave_addr, start_addr, value_qty, out) + + elif fun_code == Const.READ_COILS: + ret = self.modbus.read_coils(slave_addr, start_addr, value_qty) + out = bytearray(value_qty) + for i in range(value_qty): + out[i] = int(ret[i]) + return out + + elif fun_code == Const.READ_DISCRETE_INPUTS: + ret = self.modbus.read_discrete_inputs(slave_addr, start_addr, value_qty) + out = bytearray(value_qty) + for i in range(value_qty): + out[i] = int(ret[i]) + return out + + elif fun_code == Const.READ_HOLDING_REGISTERS: + ret = self.modbus.read_holding_registers(slave_addr, start_addr, value_qty) + out = bytearray(value_qty) + for i in range(value_qty): + out[i] = int(ret[i]) + return out + + elif fun_code == Const.READ_INPUT_REGISTER: + ret = self.modbus.read_input_registers(slave_addr, start_addr, value_qty, signed=True) + out = bytearray(value_qty) + for i in range(value_qty): + out[i] = int(ret[i]) + return out - def writeMultipleRegisters(self, slave_addr, starting_address, reg_quantity, data): - return self.modbus.write_multiple_registers(slave_addr, starting_address, data, signed=True) \ No newline at end of file + else: + raise Exception('Unsupported function code: {}'.format(fun_code)) \ No newline at end of file diff --git a/modules/modbus/uModBusFunctions.py b/modules/modbus/uModBusFunctions.py index ddaa6069b8..be6798fd57 100644 --- a/modules/modbus/uModBusFunctions.py +++ b/modules/modbus/uModBusFunctions.py @@ -1,6 +1,6 @@ #Source: https://github.com/pycom/pycom-modbus/tree/master/uModbus (2018-07-16) -import modbus.uModBusConst as Const +import modbus.uModbusConst as Const import struct def read_coils(starting_address, quantity): @@ -52,7 +52,7 @@ def write_multiple_coils(starting_address, value_list): byte_count = len(value_list) // 8 + 1 else: byte_count = len(value_list) // 8 - + return struct.pack('>BHHB' + fmt, Const.WRITE_MULTIPLE_COILS, starting_address, len(value_list), byte_count, *output_value) diff --git a/modules/modbus/uModBusSerial.py b/modules/modbus/uModBusSerial.py index 27cf3c74d5..1835b036c8 100644 --- a/modules/modbus/uModBusSerial.py +++ b/modules/modbus/uModBusSerial.py @@ -1,15 +1,15 @@ #Source: https://github.com/pycom/pycom-modbus/tree/master/uModbus (2018-07-16) #This file has been modified and differ from its source version. -import modbus.uModBusFunctions as functions -import modbus.uModBusConst as Const +import modbus.uModbusFunctions as functions +import modbus.uModbusConst as Const from machine import UART from machine import Pin import struct import time import machine -class uModBusSerial: +class uModbusSerial: def __init__(self, uart_id, baudrate=9600, data_bits=8, stop_bits=1, parity=None, pins=None, ctrl_pin=None): if pins is None: diff --git a/modules/modbus/uModBusTCP.py b/modules/modbus/uModBusTCP.py index c848605182..f25eec69d3 100644 --- a/modules/modbus/uModBusTCP.py +++ b/modules/modbus/uModBusTCP.py @@ -2,13 +2,13 @@ #This file has been modified and differ from its source version. -import modbus.uModBusFunctions as functions -import modbus.uModBusConst as Const +import modbus.uModbusFunctions as functions +import modbus.uModbusConst as Const import struct import socket import random -class uModBusTCP: +class uModbusTCP: def __init__(self, slave_ip, slave_port=502, timeout=5): self._sock = socket.socket() diff --git a/modules/modules.mk b/modules/modules.mk index e0d82d7bcf..f1d68b4e1f 100644 --- a/modules/modules.mk +++ b/modules/modules.mk @@ -145,9 +145,10 @@ ifeq ($(MICROPY_PY_ULOG), y) endif -SRC_HAAS += $(foreach dir, $(SRCDIRS_HAAS), $(wildcard $(dir)/*.c)) -# $(info 'SRC_HAAS = $(SRC_HAAS)') +include $(MODULES_DIR)/utility/component.mk +include $(MODULES_DIR)/hmac/component.mk -INC += $(addprefix -I, $(INC_HAAS)) -# $(info 'INC = $(INC)') +include $(MODULES_DIR)/system/component.mk + +INC += $(addprefix -I, ${INC_HAAS}) diff --git a/modules/system/component.mk b/modules/system/component.mk new file mode 100644 index 0000000000..536c3c52d1 --- /dev/null +++ b/modules/system/component.mk @@ -0,0 +1,15 @@ +# +# Component Makefile +# +COMPONENT_DIR = system +COMPONENT_SUBMODULES += $(COMPONENT_DIR) + +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := src + +INC_HAAS += $(addprefix $(MODULES_DIR)/$(COMPONENT_DIR)/, $(COMPONENT_ADD_INCLUDEDIRS)) +SRC_HAAS += $(addprefix modules/$(COMPONENT_DIR)/$(COMPONENT_SRCDIRS)/,\ + modsystem.c \ +) + +CFLAGS += -DMICROPY_PY_SYSTEM=1 \ No newline at end of file diff --git a/modules/system/include/system.h b/modules/system/include/system.h new file mode 100644 index 0000000000..cc4847ef42 --- /dev/null +++ b/modules/system/include/system.h @@ -0,0 +1,6 @@ +#ifndef _HAAS_PYTHON_SYSTEM_H_ +#define _HAAS_PYTHON_SYSTEM_H_ + +#define SYSINFO_SYSTEM_VERSION "2.3.0" + +#endif \ No newline at end of file diff --git a/modules/system/modsystem.c b/modules/system/modsystem.c deleted file mode 100644 index e0ffa81985..0000000000 --- a/modules/system/modsystem.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#include - -#if MICROPY_PY_SYSTEM - -#include "amp_utils.h" -#include "aos/kernel.h" -#include "aos_system.h" -#include "genhdr/mpversion.h" -#include "py/builtin.h" -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "py/obj.h" -#include "py/objstr.h" -#include "py/objtuple.h" -#include "py/runtime.h" -#include "ulog/ulog.h" - -#define LOG_TAG "MOD_SYSTEM" - -STATIC const qstr os_uname_info_fields[] = { MP_QSTR_sysname, MP_QSTR_nodename, MP_QSTR_release, MP_QSTR_version, - MP_QSTR_machine }; -STATIC const MP_DEFINE_STR_OBJ(os_uname_info_sysname_obj, MICROPY_PY_SYS_PLATFORM); -STATIC const MP_DEFINE_STR_OBJ(os_uname_info_nodename_obj, MICROPY_PY_SYS_NODE); -STATIC const MP_DEFINE_STR_OBJ(os_uname_info_release_obj, MICROPY_VERSION_STRING); -STATIC const MP_DEFINE_STR_OBJ(os_uname_info_version_obj, MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE); -STATIC const MP_DEFINE_STR_OBJ(os_uname_info_machine_obj, MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME); - -STATIC MP_DEFINE_ATTRTUPLE(os_uname_info_obj, os_uname_info_fields, 5, (mp_obj_t)&os_uname_info_sysname_obj, - (mp_obj_t)&os_uname_info_nodename_obj, (mp_obj_t)&os_uname_info_release_obj, - (mp_obj_t)&os_uname_info_version_obj, (mp_obj_t)&os_uname_info_machine_obj); - -STATIC mp_obj_t obj_getInfo() -{ - return (mp_obj_t)&os_uname_info_obj; -} -MP_DEFINE_CONST_FUN_OBJ_0(native_get_system_info, obj_getInfo); - -STATIC mp_obj_t obj_sleep(void) -{ - // aos_system_sleep(); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(native_sleep_system, obj_sleep); - -STATIC mp_obj_t obj_reboot(void) -{ - aos_reboot(); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(native_reset_system, obj_reboot); - -STATIC mp_obj_t obj_getUptime() -{ - uint64_t begin_ms = aos_now_ms(); - return MP_ROM_INT(begin_ms); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(native_get_system_uptime, obj_getUptime); - -STATIC mp_obj_t obj_getMemory() -{ - int32_t ret = -1; - amp_heap_info_t heap_info; - - ret = amp_heap_memory_info(&heap_info); - if (ret != 0) { - LOGE(LOG_TAG, "get heap memory failed"); - return mp_const_none; - } - mp_obj_t dict = mp_obj_new_dict(3); - mp_obj_dict_store(dict, mp_obj_new_str("total", strlen("total")), mp_obj_new_int(heap_info.heap_total)); - mp_obj_dict_store(dict, mp_obj_new_str("used", strlen("used")), mp_obj_new_int(heap_info.heap_used)); - mp_obj_dict_store(dict, mp_obj_new_str("free", strlen("free")), mp_obj_new_int(heap_info.heap_free)); - return dict; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(native_get_memory_info, obj_getMemory); - -STATIC const mp_rom_map_elem_t system_module_globals_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_system) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_getInfo), MP_ROM_PTR(&native_get_system_info) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&native_sleep_system) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_reboot), MP_ROM_PTR(&native_reset_system) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_getUptime), MP_ROM_PTR(&native_get_system_uptime) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_getMemory), MP_ROM_PTR(&native_get_memory_info) }, -}; - -STATIC MP_DEFINE_CONST_DICT(system_module_globals, system_module_globals_table); - -const mp_obj_module_t system_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&system_module_globals, -}; - -MP_REGISTER_MODULE(MP_QSTR_system, system_module, MICROPY_PY_SYSTEM); - -#endif // MICROPY_PY_SYSTEM \ No newline at end of file diff --git a/modules/system/src/modsystem.c b/modules/system/src/modsystem.c new file mode 100644 index 0000000000..a9100365e2 --- /dev/null +++ b/modules/system/src/modsystem.c @@ -0,0 +1,53 @@ +/* + HaaS Python +*/ + +#if MICROPY_PY_SYSTEM +#include +#include +#include + +#include "genhdr/mpversion.h" +#include "py/mpconfig.h" +#include "py/objstr.h" +#include "system.h" + +STATIC const MP_DEFINE_STR_OBJ(version_system_obj, MICROPY_SW_VENDOR_NAME "-v" SYSINFO_SYSTEM_VERSION); + +#ifdef MICROPY_HW_BOARD_TYPE +STATIC const MP_DEFINE_STR_OBJ(version_info_system_obj, + MICROPY_SW_VENDOR_NAME "-" MICROPY_HW_MCU_NAME "-" MICROPY_HW_BOARD_TYPE \ + "-v" SYSINFO_SYSTEM_VERSION "-" MICROPY_BUILD_DATE); +#else +STATIC const MP_DEFINE_STR_OBJ(version_info_system_obj, MICROPY_SW_VENDOR_NAME "-" MICROPY_HW_MCU_NAME \ + "-v" SYSINFO_SYSTEM_VERSION "-" MICROPY_BUILD_DATE); +#endif + +STATIC mp_obj_t system_version(void) +{ + return MP_OBJ_FROM_PTR(&version_system_obj); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(system_version_obj, system_version); + +STATIC mp_obj_t system_version_info(void) +{ + return MP_OBJ_FROM_PTR(&version_info_system_obj); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(system_version_info_obj, system_version_info); + +STATIC const mp_rom_map_elem_t system_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_system) }, + { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&system_version_obj) }, + { MP_ROM_QSTR(MP_QSTR_version_info), MP_ROM_PTR(&system_version_info_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(system_module_globals, system_module_globals_table); + +const mp_obj_module_t system_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&system_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_system, system_module, MICROPY_PY_SYSTEM); + +#endif // MICROPY_PY_SYSTEM \ No newline at end of file diff --git a/modules/ulinksdk/aliyunIoT_test.py b/modules/ualiyunIoT/aliyunIoT.py similarity index 65% rename from modules/ulinksdk/aliyunIoT_test.py rename to modules/ualiyunIoT/aliyunIoT.py index 0175610955..8ceb274558 100644 --- a/modules/ulinksdk/aliyunIoT_test.py +++ b/modules/ualiyunIoT/aliyunIoT.py @@ -1,4 +1,5 @@ -import linksdk +from ulinksdk import linksdk +import utime class Device: # 事件类型(eventType) @@ -18,8 +19,9 @@ class Device: def __init__(self): self.linksdk = linksdk.LinkSDK() self.callback = [None] * (self.ON_CB_MAX) + self.linksdk.lk_set_oncallback(self.callback) self.deviceHandle = [self.callback, self.linksdk] - + def register(self, deviceinfo, callback): if (self.linksdk == None): print("device not connected!") @@ -31,10 +33,10 @@ def register(self, deviceinfo, callback): def on(self, Type, cb): self.callback[Type] = cb + return 0 def connect(self, key_info): - self.linksdk.lk_set_oncallback(self.callback) - self.linksdk.lk_connect(key_info) + return self.linksdk.lk_connect(key_info) def getDeviceInfo(self): if (self.linksdk == None): @@ -53,41 +55,41 @@ def getDeviceHandle(self): def getNtpTime(self, cb): if (self.linksdk == None): print("device not connected!") - return (-1) + return (-1) try: return self.linksdk.lk_getNTPtime(cb) except: pass - def postProps(self, post_data): + def postProps(self, post_data): if (self.linksdk == None): print("device not connected!") - return (-1) + return (-1) try: - self.linksdk.lk_postProps(post_data) - return 0 + return self.linksdk.lk_postProps(post_data) except: - pass + print("post props error!!!!") + return -1 def postEvent(self, event_data): if (self.linksdk == None): print("device not connected!") - return (-1) + return (-1) try: - self.linksdk.lk_postEvent(event_data) - return 0 + return self.linksdk.lk_postEvent(event_data) except: - pass - + print("post event error!!!!") + return -1 + def postRaw(self, raw_data): if (self.linksdk == None): print("device not connected!") - return (-1) + return (-1) try: - self.linksdk.lk_postRaw(raw_data) - return 0 + return self.linksdk.lk_postRaw(raw_data) except: - pass + print("post raw error!!!!") + return -1 def uploadFile(self, filename, filepath, cb): if (self.linksdk == None): @@ -96,6 +98,7 @@ def uploadFile(self, filename, filepath, cb): try: return self.linksdk.lk_upload(filename, filepath, 0, cb) except: + print("uploadFile error!!!!") return None def uploadContent(self, filename, content, cb): @@ -108,38 +111,55 @@ def uploadContent(self, filename, content, cb): data_len = len(content) return self.linksdk.lk_upload(filename, content, data_len, cb) except: + print("uploadContent error!!!!") return None def subscribe(self, topicinfo): if (self.linksdk == None): print("device not connected!") return (-1) - return self.linksdk.lk_subscribe( - topic = topicinfo['topic'], - qos = topicinfo['qos'] - ) + try: + return self.linksdk.lk_subscribe( + topic = topicinfo['topic'], + qos = topicinfo['qos'] + ) + except: + print("subscribe error!!!!") + return -1 def publish(self, topicinfo): if (self.linksdk == None): print("device not connected!") return (-1) - - return self.linksdk.lk_publish( - topic = topicinfo['topic'], - msg = topicinfo['payload'], - qos = topicinfo['qos'] - ) + try: + return self.linksdk.lk_publish( + topic = topicinfo['topic'], + msg = topicinfo['payload'], + qos = topicinfo['qos'] + ) + except: + print("publish error!!!!") + return -1 def unsubscribe(self,topicinfo): if (self.linksdk == None): print("device not connected!") return (-1) - - return self.linksdk.lk_unsubscribe( - topic = topicinfo['topic'], - qos = topicinfo['qos'] - ) - + try: + return self.linksdk.lk_unsubscribe( + topic = topicinfo['topic'], + qos = topicinfo['qos'] + ) + except: + print("unsubscribe error!!!!") + return -1 def end(self): - self.linksdk.mqtt_client.disconnect() + self.linksdk.exit = True + self.linksdk.lk_disconnect() + + def onService(self): + pass + + def onProps(self): + pass diff --git a/modules/ukv/kv.py b/modules/ukv/kv.py new file mode 100644 index 0000000000..19195bfa1b --- /dev/null +++ b/modules/ukv/kv.py @@ -0,0 +1,71 @@ +import uio as io +import ujson as json +import systemAdaptor + +def set(key, value): + kvPath = systemAdaptor.getDataPath() + "kv.json" + kvList = {} + try: + f = open(kvPath, 'r') + kvList = json.loads(f.read()) + f.close() + except Exception as e: + pass + + kvList[key] = value + try: + f = open(kvPath, 'w') + f.write(json.dumps(kvList)) + f.flush() + except Exception as e: + print(str(e)) + f.close() + return 0 + +def get(key): + kvPath = systemAdaptor.getDataPath() + "kv.json" + f = open(kvPath, 'r') + kvList = json.loads(f.read()) + f.close() + if key in kvList.keys(): + return kvList[key] + else: + return None + +def remove(key): + kvPath = systemAdaptor.getDataPath() + "kv.json" + kvList = {} + try: + f = open(kvPath, 'r') + kvList = json.loads(f.read()) + f.close() + except Exception as e: + print(str(e)) + return + + if key in kvList.keys(): + del kvList[key] + else: + return 0 + + try: + f = open(kvPath, 'w') + f.write(json.dumps(kvList)) + f.flush() + except Exception as e: + print(str(e)) + f.close() + + del kvList + return 0 + +def list(): + kvPath = systemAdaptor.getDataPath() + "kv.json" + kvList = {} + try: + f = open(kvPath, 'r') + kvList = json.loads(f.read()) + f.close() + except Exception as e: + pass + return kvList diff --git a/modules/ulinksdk/_uhttp/__init__.py b/modules/ulinksdk/__init__.py similarity index 100% rename from modules/ulinksdk/_uhttp/__init__.py rename to modules/ulinksdk/__init__.py diff --git a/modules/ulinksdk/_uhttp/client.py b/modules/ulinksdk/_uhttp/client.py deleted file mode 100644 index 5d30cbfe0c..0000000000 --- a/modules/ulinksdk/_uhttp/client.py +++ /dev/null @@ -1,1333 +0,0 @@ -"""HTTP/1.1 client library - - - - -HTTPConnection goes through a number of "states", which define when a client -may legally make another request or fetch the response for a particular -request. This diagram details these state transitions: - - (null) - | - | HTTPConnection() - v - Idle - | - | putrequest() - v - Request-started - | - | ( putheader() )* endheaders() - v - Request-sent - | - | response = getresponse() - v - Unread-response [Response-headers-read] - |\____________________ - | | - | response.read() | putrequest() - v v - Idle Req-started-unread-response - ______/| - / | - response.read() | | ( putheader() )* endheaders() - v v - Request-started Req-sent-unread-response - | - | response.read() - v - Request-sent - -This diagram presents the following rules: - -- a second request may not be started until {response-headers-read} - -- a response [object] cannot be retrieved until {request-sent} - -- there is no differentiation between an unread response body and a - partially read response body - -Note: this enforcement is applied by the HTTPConnection class. The - HTTPResponse class does not enforce this state machine, which - implies sophisticated clients may accelerate the request/response - pipeline. Caution should be taken, though: accelerating the states - beyond the above pattern may imply knowledge of the server's - connection-close behavior for certain requests. For example, it - is impossible to tell whether the server will close the connection - UNTIL the response headers have been read; this means that further - requests cannot be placed into the pipeline until it is known that - the server will NOT be closing the connection. - -Logical State __state __response -------------- ------- ---------- -Idle _CS_IDLE None -Request-started _CS_REQ_STARTED None -Request-sent _CS_REQ_SENT None -Unread-response _CS_IDLE -Req-started-unread-response _CS_REQ_STARTED -Req-sent-unread-response _CS_REQ_SENT -""" - -import email.parser -import email.message -import io -import os -import socket -import collections -from urllib.parse import urlsplit -import warnings - -__all__ = [ - "HTTPResponse", - "HTTPConnection", - "HTTPException", - "NotConnected", - "UnknownProtocol", - "UnknownTransferEncoding", - "UnimplementedFileMode", - "IncompleteRead", - "InvalidURL", - "ImproperConnectionState", - "CannotSendRequest", - "CannotSendHeader", - "ResponseNotReady", - "BadStatusLine", - "error", - "responses", -] - -HTTP_PORT = 80 -HTTPS_PORT = 443 - -_UNKNOWN = "UNKNOWN" - -# connection states -_CS_IDLE = "Idle" -_CS_REQ_STARTED = "Request-started" -_CS_REQ_SENT = "Request-sent" - -# status codes -# informational -CONTINUE = 100 -SWITCHING_PROTOCOLS = 101 -PROCESSING = 102 - -# successful -OK = 200 -CREATED = 201 -ACCEPTED = 202 -NON_AUTHORITATIVE_INFORMATION = 203 -NO_CONTENT = 204 -RESET_CONTENT = 205 -PARTIAL_CONTENT = 206 -MULTI_STATUS = 207 -IM_USED = 226 - -# redirection -MULTIPLE_CHOICES = 300 -MOVED_PERMANENTLY = 301 -FOUND = 302 -SEE_OTHER = 303 -NOT_MODIFIED = 304 -USE_PROXY = 305 -TEMPORARY_REDIRECT = 307 - -# client error -BAD_REQUEST = 400 -UNAUTHORIZED = 401 -PAYMENT_REQUIRED = 402 -FORBIDDEN = 403 -NOT_FOUND = 404 -METHOD_NOT_ALLOWED = 405 -NOT_ACCEPTABLE = 406 -PROXY_AUTHENTICATION_REQUIRED = 407 -REQUEST_TIMEOUT = 408 -CONFLICT = 409 -GONE = 410 -LENGTH_REQUIRED = 411 -PRECONDITION_FAILED = 412 -REQUEST_ENTITY_TOO_LARGE = 413 -REQUEST_URI_TOO_LONG = 414 -UNSUPPORTED_MEDIA_TYPE = 415 -REQUESTED_RANGE_NOT_SATISFIABLE = 416 -EXPECTATION_FAILED = 417 -UNPROCESSABLE_ENTITY = 422 -LOCKED = 423 -FAILED_DEPENDENCY = 424 -UPGRADE_REQUIRED = 426 -PRECONDITION_REQUIRED = 428 -TOO_MANY_REQUESTS = 429 -REQUEST_HEADER_FIELDS_TOO_LARGE = 431 - -# server error -INTERNAL_SERVER_ERROR = 500 -NOT_IMPLEMENTED = 501 -BAD_GATEWAY = 502 -SERVICE_UNAVAILABLE = 503 -GATEWAY_TIMEOUT = 504 -HTTP_VERSION_NOT_SUPPORTED = 505 -INSUFFICIENT_STORAGE = 507 -NOT_EXTENDED = 510 -NETWORK_AUTHENTICATION_REQUIRED = 511 - -# Mapping status codes to official W3C names -responses = { - 100: "Continue", - 101: "Switching Protocols", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non-Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 300: "Multiple Choices", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 306: "(Unused)", - 307: "Temporary Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request-URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 428: "Precondition Required", - 429: "Too Many Requests", - 431: "Request Header Fields Too Large", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 511: "Network Authentication Required", -} - -# maximal amount of data to read at one time in _safe_read -MAXAMOUNT = 1048576 - -# maximal line length when calling readline(). -_MAXLINE = 65536 -_MAXHEADERS = 100 - - -class HTTPMessage(email.message.Message): - # XXX The only usage of this method is in - # http.server.CGIHTTPRequestHandler. Maybe move the code there so - # that it doesn't need to be part of the public API. The API has - # never been defined so this could cause backwards compatibility - # issues. - - def getallmatchingheaders(self, name): - """Find all header lines matching a given header name. - - Look through the list of headers and find all lines matching a given - header name (and their continuation lines). A list of the lines is - returned, without interpretation. If the header does not occur, an - empty list is returned. If the header occurs multiple times, all - occurrences are returned. Case is not important in the header name. - - """ - name = name.lower() + ":" - n = len(name) - lst = [] - hit = 0 - for line in self.keys(): - if line[:n].lower() == name: - hit = 1 - elif not line[:1].isspace(): - hit = 0 - if hit: - lst.append(line) - return lst - - -def parse_headers(fp, _class=HTTPMessage): - """Parses only RFC2822 headers from a file pointer. - - email Parser wants to see strings rather than bytes. - But a TextIOWrapper around self.rfile would buffer too many bytes - from the stream, bytes which we later need to read as bytes. - So we read the correct bytes here, as bytes, for email Parser - to parse. - - """ - headers = [] - while True: - line = fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise LineTooLong("header line") - headers.append(line) - if len(headers) > _MAXHEADERS: - raise HTTPException("got more than %d headers" % _MAXHEADERS) - if line in (b"\r\n", b"\n", b""): - break - hstring = b"".join(headers).decode("iso-8859-1") - return email.parser.Parser(_class=_class).parsestr(hstring) - - -_strict_sentinel = object() - - -class HTTPResponse: - # class HTTPResponse(io.RawIOBase): - - # See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details. - - # The bytes from the socket object are iso-8859-1 strings. - # See RFC 2616 sec 2.2 which notes an exception for MIME-encoded - # text following RFC 2047. The basic status line parsing only - # accepts iso-8859-1. - - def __init__(self, sock, debuglevel=0, strict=_strict_sentinel, method=None, url=None): - # If the response includes a content-length header, we need to - # make sure that the client doesn't read more than the - # specified number of bytes. If it does, it will block until - # the server times out and closes the connection. This will - # happen if a self.fp.read() is done (without a size) whether - # self.fp is buffered or not. So, no self.fp.read() by - # clients unless they know what they are doing. - self.fp = sock.makefile("rb") - self.debuglevel = debuglevel - if strict is not _strict_sentinel: - warnings.warn( - "the 'strict' argument isn't supported anymore; " - "http.client now always assumes HTTP/1.x compliant servers.", - DeprecationWarning, - 2, - ) - self._method = method - - # The HTTPResponse object is returned via urllib. The clients - # of http and urllib expect different attributes for the - # headers. headers is used here and supports urllib. msg is - # provided as a backwards compatibility layer for http - # clients. - - self.headers = self.msg = None - - # from the Status-Line of the response - self.version = _UNKNOWN # HTTP-Version - self.status = _UNKNOWN # Status-Code - self.reason = _UNKNOWN # Reason-Phrase - - self.chunked = _UNKNOWN # is "chunked" being used? - self.chunk_left = _UNKNOWN # bytes left to read in current chunk - self.length = _UNKNOWN # number of bytes left in response - self.will_close = _UNKNOWN # conn will close at end of response - - def _read_status(self): - line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1") - if len(line) > _MAXLINE: - raise LineTooLong("status line") - if self.debuglevel > 0: - print("reply:", repr(line)) - if not line: - # Presumably, the server closed the connection before - # sending a valid response. - raise BadStatusLine(line) - try: - version, status, reason = line.split(None, 2) - except ValueError: - try: - version, status = line.split(None, 1) - reason = "" - except ValueError: - # empty version will cause next test to fail. - version = "" - if not version.startswith("HTTP/"): - self._close_conn() - raise BadStatusLine(line) - - # The status code is a three-digit number - try: - status = int(status) - if status < 100 or status > 999: - raise BadStatusLine(line) - except ValueError: - raise BadStatusLine(line) - return version, status, reason - - def begin(self): - if self.headers is not None: - # we've already started reading the response - return - - # read until we get a non-100 response - while True: - version, status, reason = self._read_status() - if status != CONTINUE: - break - # skip the header from the 100 response - while True: - skip = self.fp.readline(_MAXLINE + 1) - if len(skip) > _MAXLINE: - raise LineTooLong("header line") - skip = skip.strip() - if not skip: - break - if self.debuglevel > 0: - print("header:", skip) - - self.code = self.status = status - self.reason = reason.strip() - if version in ("HTTP/1.0", "HTTP/0.9"): - # Some servers might still return "0.9", treat it as 1.0 anyway - self.version = 10 - elif version.startswith("HTTP/1."): - self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1 - else: - raise UnknownProtocol(version) - - self.headers = self.msg = parse_headers(self.fp) - - if self.debuglevel > 0: - for hdr in self.headers: - print("header:", hdr, end=" ") - - # are we using the chunked-style of transfer encoding? - tr_enc = self.headers.get("transfer-encoding") - if tr_enc and tr_enc.lower() == "chunked": - self.chunked = True - self.chunk_left = None - else: - self.chunked = False - - # will the connection close at the end of the response? - self.will_close = self._check_close() - - # do we have a Content-Length? - # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked" - self.length = None - length = self.headers.get("content-length") - - # are we using the chunked-style of transfer encoding? - tr_enc = self.headers.get("transfer-encoding") - if length and not self.chunked: - try: - self.length = int(length) - except ValueError: - self.length = None - else: - if self.length < 0: # ignore nonsensical negative lengths - self.length = None - else: - self.length = None - - # does the body have a fixed length? (of zero) - if ( - status == NO_CONTENT - or status == NOT_MODIFIED - or 100 <= status < 200 - or self._method == "HEAD" # 1xx codes - ): - self.length = 0 - - # if the connection remains open, and we aren't using chunked, and - # a content-length was not provided, then assume that the connection - # WILL close. - if not self.will_close and not self.chunked and self.length is None: - self.will_close = True - - def _check_close(self): - conn = self.headers.get("connection") - if self.version == 11: - # An HTTP/1.1 proxy is assumed to stay open unless - # explicitly closed. - conn = self.headers.get("connection") - if conn and "close" in conn.lower(): - return True - return False - - # Some HTTP/1.0 implementations have support for persistent - # connections, using rules different than HTTP/1.1. - - # For older HTTP, Keep-Alive indicates persistent connection. - if self.headers.get("keep-alive"): - return False - - # At least Akamai returns a "Connection: Keep-Alive" header, - # which was supposed to be sent by the client. - if conn and "keep-alive" in conn.lower(): - return False - - # Proxy-Connection is a netscape hack. - pconn = self.headers.get("proxy-connection") - if pconn and "keep-alive" in pconn.lower(): - return False - - # otherwise, assume it will close - return True - - def _close_conn(self): - fp = self.fp - self.fp = None - fp.close() - - def close(self): - super().close() # set "closed" flag - if self.fp: - self._close_conn() - - # These implementations are for the benefit of io.BufferedReader. - - # XXX This class should probably be revised to act more like - # the "raw stream" that BufferedReader expects. - - def flush(self): - super().flush() - if self.fp: - self.fp.flush() - - def readable(self): - return True - - # End of "raw stream" methods - - def isclosed(self): - """True if the connection is closed.""" - # NOTE: it is possible that we will not ever call self.close(). This - # case occurs when will_close is TRUE, length is None, and we - # read up to the last byte, but NOT past it. - # - # IMPLIES: if will_close is FALSE, then self.close() will ALWAYS be - # called, meaning self.isclosed() is meaningful. - return self.fp is None - - def read(self, amt=None): - if self.fp is None: - return b"" - - if self._method == "HEAD": - self._close_conn() - return b"" - - if amt is not None: - # Amount is given, so call base class version - # (which is implemented in terms of self.readinto) - return super(HTTPResponse, self).read(amt) - else: - # Amount is not given (unbounded read) so we must check self.length - # and self.chunked - - if self.chunked: - return self._readall_chunked() - - if self.length is None: - s = self.fp.read() - else: - try: - s = self._safe_read(self.length) - except IncompleteRead: - self._close_conn() - raise - self.length = 0 - self._close_conn() # we read everything - return s - - def readinto(self, b): - if self.fp is None: - return 0 - - if self._method == "HEAD": - self._close_conn() - return 0 - - if self.chunked: - return self._readinto_chunked(b) - - if self.length is not None: - if len(b) > self.length: - # clip the read to the "end of response" - b = memoryview(b)[0 : self.length] - - # we do not use _safe_read() here because this may be a .will_close - # connection, and the user is reading more bytes than will be provided - # (for example, reading in 1k chunks) - n = self.fp.readinto(b) - if not n: - # Ideally, we would raise IncompleteRead if the content-length - # wasn't satisfied, but it might break compatibility. - self._close_conn() - elif self.length is not None: - self.length -= n - if not self.length: - self._close_conn() - return n - - def _read_next_chunk_size(self): - # Read the next chunk size from the file - line = self.fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise LineTooLong("chunk size") - i = line.find(b";") - if i >= 0: - line = line[:i] # strip chunk-extensions - try: - return int(line, 16) - except ValueError: - # close the connection as protocol synchronisation is - # probably lost - self._close_conn() - raise - - def _read_and_discard_trailer(self): - # read and discard trailer up to the CRLF terminator - ### note: we shouldn't have any trailers! - while True: - line = self.fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise LineTooLong("trailer line") - if not line: - # a vanishingly small number of sites EOF without - # sending the trailer - break - if line in (b"\r\n", b"\n", b""): - break - - def _readall_chunked(self): - assert self.chunked != _UNKNOWN - chunk_left = self.chunk_left - value = [] - while True: - if chunk_left is None: - try: - chunk_left = self._read_next_chunk_size() - if chunk_left == 0: - break - except ValueError: - raise IncompleteRead(b"".join(value)) - value.append(self._safe_read(chunk_left)) - - # we read the whole chunk, get another - self._safe_read(2) # toss the CRLF at the end of the chunk - chunk_left = None - - self._read_and_discard_trailer() - - # we read everything; close the "file" - self._close_conn() - - return b"".join(value) - - def _readinto_chunked(self, b): - assert self.chunked != _UNKNOWN - chunk_left = self.chunk_left - - total_bytes = 0 - mvb = memoryview(b) - while True: - if chunk_left is None: - try: - chunk_left = self._read_next_chunk_size() - if chunk_left == 0: - break - except ValueError: - raise IncompleteRead(bytes(b[0:total_bytes])) - - if len(mvb) < chunk_left: - n = self._safe_readinto(mvb) - self.chunk_left = chunk_left - n - return total_bytes + n - elif len(mvb) == chunk_left: - n = self._safe_readinto(mvb) - self._safe_read(2) # toss the CRLF at the end of the chunk - self.chunk_left = None - return total_bytes + n - else: - temp_mvb = mvb[0:chunk_left] - n = self._safe_readinto(temp_mvb) - mvb = mvb[n:] - total_bytes += n - - # we read the whole chunk, get another - self._safe_read(2) # toss the CRLF at the end of the chunk - chunk_left = None - - self._read_and_discard_trailer() - - # we read everything; close the "file" - self._close_conn() - - return total_bytes - - def _safe_read(self, amt): - """Read the number of bytes requested, compensating for partial reads. - - Normally, we have a blocking socket, but a read() can be interrupted - by a signal (resulting in a partial read). - - Note that we cannot distinguish between EOF and an interrupt when zero - bytes have been read. IncompleteRead() will be raised in this - situation. - - This function should be used when bytes "should" be present for - reading. If the bytes are truly not available (due to EOF), then the - IncompleteRead exception can be used to detect the problem. - """ - s = [] - while amt > 0: - chunk = self.fp.read(min(amt, MAXAMOUNT)) - if not chunk: - raise IncompleteRead(b"".join(s), amt) - s.append(chunk) - amt -= len(chunk) - return b"".join(s) - - def _safe_readinto(self, b): - """Same as _safe_read, but for reading into a buffer.""" - total_bytes = 0 - mvb = memoryview(b) - while total_bytes < len(b): - if MAXAMOUNT < len(mvb): - temp_mvb = mvb[0:MAXAMOUNT] - n = self.fp.readinto(temp_mvb) - else: - n = self.fp.readinto(mvb) - if not n: - raise IncompleteRead(bytes(mvb[0:total_bytes]), len(b)) - mvb = mvb[n:] - total_bytes += n - return total_bytes - - def fileno(self): - return self.fp.fileno() - - def getheader(self, name, default=None): - if self.headers is None: - raise ResponseNotReady() - headers = self.headers.get_all(name) or default - if isinstance(headers, str) or not hasattr(headers, "__iter__"): - return headers - else: - return ", ".join(headers) - - def getheaders(self): - """Return list of (header, value) tuples.""" - if self.headers is None: - raise ResponseNotReady() - return list(self.headers.items()) - - # We override IOBase.__iter__ so that it doesn't check for closed-ness - - def __iter__(self): - return self - - # For compatibility with old-style urllib responses. - - def info(self): - return self.headers - - def geturl(self): - return self.url - - def getcode(self): - return self.status - - -class HTTPConnection: - - _http_vsn = 11 - _http_vsn_str = "HTTP/1.1" - - response_class = HTTPResponse - default_port = HTTP_PORT - auto_open = 1 - debuglevel = 0 - - def __init__( - self, - host, - port=None, - strict=_strict_sentinel, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None, - ): - if strict is not _strict_sentinel: - warnings.warn( - "the 'strict' argument isn't supported anymore; " - "http.client now always assumes HTTP/1.x compliant servers.", - DeprecationWarning, - 2, - ) - self.timeout = timeout - self.source_address = source_address - self.sock = None - self._buffer = [] - self.__response = None - self.__state = _CS_IDLE - self._method = None - self._tunnel_host = None - self._tunnel_port = None - self._tunnel_headers = {} - - self._set_hostport(host, port) - - def set_tunnel(self, host, port=None, headers=None): - """Sets up the host and the port for the HTTP CONNECT Tunnelling. - - The headers argument should be a mapping of extra HTTP headers - to send with the CONNECT request. - """ - self._tunnel_host = host - self._tunnel_port = port - if headers: - self._tunnel_headers = headers - else: - self._tunnel_headers.clear() - - def _set_hostport(self, host, port): - if port is None: - i = host.rfind(":") - j = host.rfind("]") # ipv6 addresses have [...] - if i > j: - try: - port = int(host[i + 1 :]) - except ValueError: - if host[i + 1 :] == "": # http://foo.com:/ == http://foo.com/ - port = self.default_port - else: - raise InvalidURL("nonnumeric port: '%s'" % host[i + 1 :]) - host = host[:i] - else: - port = self.default_port - if host and host[0] == "[" and host[-1] == "]": - host = host[1:-1] - self.host = host - self.port = port - - def set_debuglevel(self, level): - self.debuglevel = level - - def _tunnel(self): - self._set_hostport(self._tunnel_host, self._tunnel_port) - connect_str = "CONNECT %s:%d HTTP/1.0\r\n" % (self.host, self.port) - connect_bytes = connect_str.encode("ascii") - self.send(connect_bytes) - for header, value in self._tunnel_headers.items(): - header_str = "%s: %s\r\n" % (header, value) - header_bytes = header_str.encode("latin-1") - self.send(header_bytes) - self.send(b"\r\n") - - response = self.response_class(self.sock, method=self._method) - (version, code, message) = response._read_status() - - if code != 200: - self.close() - raise socket.error("Tunnel connection failed: %d %s" % (code, message.strip())) - while True: - line = response.fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise LineTooLong("header line") - if not line: - # for sites which EOF without sending a trailer - break - if line in (b"\r\n", b"\n", b""): - break - - def connect(self): - """Connect to the host and port specified in __init__.""" - self.sock = socket.create_connection( - (self.host, self.port), self.timeout, self.source_address - ) - if self._tunnel_host: - self._tunnel() - - def close(self): - """Close the connection to the HTTP server.""" - if self.sock: - self.sock.close() # close it manually... there may be other refs - self.sock = None - if self.__response: - self.__response.close() - self.__response = None - self.__state = _CS_IDLE - - def send(self, data): - """Send `data' to the server. - ``data`` can be a string object, a bytes object, an array object, a - file-like object that supports a .read() method, or an iterable object. - """ - - if self.sock is None: - if self.auto_open: - self.connect() - else: - raise NotConnected() - - if self.debuglevel > 0: - print("send:", repr(data)) - blocksize = 8192 - if hasattr(data, "read"): - if self.debuglevel > 0: - print("sendIng a read()able") - encode = False - try: - mode = data.mode - except AttributeError: - # io.BytesIO and other file-like objects don't have a `mode` - # attribute. - pass - else: - if "b" not in mode: - encode = True - if self.debuglevel > 0: - print("encoding file using iso-8859-1") - while 1: - datablock = data.read(blocksize) - if not datablock: - break - if encode: - datablock = datablock.encode("iso-8859-1") - self.sock.sendall(datablock) - return - try: - self.sock.sendall(data) - except TypeError: - if isinstance(data, collections.Iterable): - for d in data: - self.sock.sendall(d) - else: - raise TypeError( - "data should be a bytes-like object " "or an iterable, got %r" % type(data) - ) - - def _output(self, s): - """Add a line of output to the current request buffer. - - Assumes that the line does *not* end with \\r\\n. - """ - self._buffer.append(s) - - def _send_output(self, message_body=None): - """Send the currently buffered request and clear the buffer. - - Appends an extra \\r\\n to the buffer. - A message_body may be specified, to be appended to the request. - """ - self._buffer.extend((b"", b"")) - msg = b"\r\n".join(self._buffer) - del self._buffer[:] - # If msg and message_body are sent in a single send() call, - # it will avoid performance problems caused by the interaction - # between delayed ack and the Nagle algorithm. - if isinstance(message_body, bytes): - msg += message_body - message_body = None - self.send(msg) - if message_body is not None: - # message_body was not a string (i.e. it is a file), and - # we must run the risk of Nagle. - self.send(message_body) - - def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): - """Send a request to the server. - - `method' specifies an HTTP request method, e.g. 'GET'. - `url' specifies the object being requested, e.g. '/index.html'. - `skip_host' if True does not add automatically a 'Host:' header - `skip_accept_encoding' if True does not add automatically an - 'Accept-Encoding:' header - """ - - # if a prior response has been completed, then forget about it. - if self.__response and self.__response.isclosed(): - self.__response = None - - # in certain cases, we cannot issue another request on this connection. - # this occurs when: - # 1) we are in the process of sending a request. (_CS_REQ_STARTED) - # 2) a response to a previous request has signalled that it is going - # to close the connection upon completion. - # 3) the headers for the previous response have not been read, thus - # we cannot determine whether point (2) is true. (_CS_REQ_SENT) - # - # if there is no prior response, then we can request at will. - # - # if point (2) is true, then we will have passed the socket to the - # response (effectively meaning, "there is no prior response"), and - # will open a new one when a new request is made. - # - # Note: if a prior response exists, then we *can* start a new request. - # We are not allowed to begin fetching the response to this new - # request, however, until that prior response is complete. - # - if self.__state == _CS_IDLE: - self.__state = _CS_REQ_STARTED - else: - raise CannotSendRequest(self.__state) - - # Save the method we use, we need it later in the response phase - self._method = method - if not url: - url = "/" - request = "%s %s %s" % (method, url, self._http_vsn_str) - - # Non-ASCII characters should have been eliminated earlier - self._output(request.encode("ascii")) - - if self._http_vsn == 11: - # Issue some standard headers for better HTTP/1.1 compliance - - if not skip_host: - # this header is issued *only* for HTTP/1.1 - # connections. more specifically, this means it is - # only issued when the client uses the new - # HTTPConnection() class. backwards-compat clients - # will be using HTTP/1.0 and those clients may be - # issuing this header themselves. we should NOT issue - # it twice; some web servers (such as Apache) barf - # when they see two Host: headers - - # If we need a non-standard port,include it in the - # header. If the request is going through a proxy, - # but the host of the actual URL, not the host of the - # proxy. - - netloc = "" - if url.startswith("http"): - nil, netloc, nil, nil, nil = urlsplit(url) - - if netloc: - try: - netloc_enc = netloc.encode("ascii") - except UnicodeEncodeError: - netloc_enc = netloc.encode("idna") - self.putheader("Host", netloc_enc) - else: - try: - host_enc = self.host.encode("ascii") - except UnicodeEncodeError: - host_enc = self.host.encode("idna") - - # As per RFC 273, IPv6 address should be wrapped with [] - # when used as Host header - - if self.host.find(":") >= 0: - host_enc = b"[" + host_enc + b"]" - - if self.port == self.default_port: - self.putheader("Host", host_enc) - else: - host_enc = host_enc.decode("ascii") - self.putheader("Host", "%s:%s" % (host_enc, self.port)) - - # note: we are assuming that clients will not attempt to set these - # headers since *this* library must deal with the - # consequences. this also means that when the supporting - # libraries are updated to recognize other forms, then this - # code should be changed (removed or updated). - - # we only want a Content-Encoding of "identity" since we don't - # support encodings such as x-gzip or x-deflate. - if not skip_accept_encoding: - self.putheader("Accept-Encoding", "identity") - - # we can accept "chunked" Transfer-Encodings, but no others - # NOTE: no TE header implies *only* "chunked" - # self.putheader('TE', 'chunked') - - # if TE is supplied in the header, then it must appear in a - # Connection header. - # self.putheader('Connection', 'TE') - - else: - # For HTTP/1.0, the server will assume "not chunked" - pass - - def putheader(self, header, *values): - """Send a request header line to the server. - - For example: h.putheader('Accept', 'text/html') - """ - if self.__state != _CS_REQ_STARTED: - raise CannotSendHeader() - - if hasattr(header, "encode"): - header = header.encode("ascii") - values = list(values) - for i, one_value in enumerate(values): - if isinstance(one_value, str): - values[i] = one_value.encode("latin-1") - elif isinstance(one_value, int): - values[i] = str(one_value).encode("ascii") - value = b"\r\n\t".join(values) - header = header + b": " + value - self._output(header) - - def endheaders(self, message_body=None): - """Indicate that the last header line has been sent to the server. - - This method sends the request to the server. The optional message_body - argument can be used to pass a message body associated with the - request. The message body will be sent in the same packet as the - message headers if it is a string, otherwise it is sent as a separate - packet. - """ - if self.__state == _CS_REQ_STARTED: - self.__state = _CS_REQ_SENT - else: - raise CannotSendHeader() - self._send_output(message_body) - - def request(self, method, url, body=None, headers={}): - """Send a complete request to the server.""" - self._send_request(method, url, body, headers) - - def _set_content_length(self, body): - # Set the content-length based on the body. - thelen = None - try: - thelen = str(len(body)) - except TypeError as te: - # If this is a file-like object, try to - # fstat its file descriptor - try: - thelen = str(os.fstat(body.fileno()).st_size) - except (AttributeError, OSError): - # Don't send a length if this failed - if self.debuglevel > 0: - print("Cannot stat!!") - - if thelen is not None: - self.putheader("Content-Length", thelen) - - def _send_request(self, method, url, body, headers): - # Honor explicitly requested Host: and Accept-Encoding: headers. - header_names = dict.fromkeys([k.lower() for k in headers]) - skips = {} - if "host" in header_names: - skips["skip_host"] = 1 - if "accept-encoding" in header_names: - skips["skip_accept_encoding"] = 1 - - self.putrequest(method, url, **skips) - - if body is not None and ("content-length" not in header_names): - self._set_content_length(body) - for hdr, value in headers.items(): - self.putheader(hdr, value) - if isinstance(body, str): - # RFC 2616 Section 3.7.1 says that text default has a - # default charset of iso-8859-1. - body = body.encode("iso-8859-1") - self.endheaders(body) - - def getresponse(self): - """Get the response from the server. - - If the HTTPConnection is in the correct state, returns an - instance of HTTPResponse or of whatever object is returned by - class the response_class variable. - - If a request has not been sent or if a previous response has - not be handled, ResponseNotReady is raised. If the HTTP - response indicates that the connection should be closed, then - it will be closed before the response is returned. When the - connection is closed, the underlying socket is closed. - """ - - # if a prior response has been completed, then forget about it. - if self.__response and self.__response.isclosed(): - self.__response = None - - # if a prior response exists, then it must be completed (otherwise, we - # cannot read this response's header to determine the connection-close - # behavior) - # - # note: if a prior response existed, but was connection-close, then the - # socket and response were made independent of this HTTPConnection - # object since a new request requires that we open a whole new - # connection - # - # this means the prior response had one of two states: - # 1) will_close: this connection was reset and the prior socket and - # response operate independently - # 2) persistent: the response was retained and we await its - # isclosed() status to become true. - # - if self.__state != _CS_REQ_SENT or self.__response: - raise ResponseNotReady(self.__state) - - if self.debuglevel > 0: - response = self.response_class(self.sock, self.debuglevel, method=self._method) - else: - response = self.response_class(self.sock, method=self._method) - - response.begin() - assert response.will_close != _UNKNOWN - self.__state = _CS_IDLE - - if response.will_close: - # this effectively passes the connection to the response - self.close() - else: - # remember this, so we can tell when it is complete - self.__response = response - - return response - - -try: - import ssl -except ImportError: - pass -else: - - class HTTPSConnection(HTTPConnection): - "This class allows communication via SSL." - - default_port = HTTPS_PORT - - # XXX Should key_file and cert_file be deprecated in favour of context? - - def __init__( - self, - host, - port=None, - key_file=None, - cert_file=None, - strict=_strict_sentinel, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None, - *, - context=None, - check_hostname=None - ): - super(HTTPSConnection, self).__init__(host, port, strict, timeout, source_address) - self.key_file = key_file - self.cert_file = cert_file - if context is None: - # Some reasonable defaults - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.options |= ssl.OP_NO_SSLv2 - will_verify = context.verify_mode != ssl.CERT_NONE - if check_hostname is None: - check_hostname = will_verify - elif check_hostname and not will_verify: - raise ValueError( - "check_hostname needs a SSL context with " - "either CERT_OPTIONAL or CERT_REQUIRED" - ) - if key_file or cert_file: - context.load_cert_chain(cert_file, key_file) - self._context = context - self._check_hostname = check_hostname - - def connect(self): - "Connect to a host on a given (SSL) port." - - sock = socket.create_connection( - (self.host, self.port), self.timeout, self.source_address - ) - - if self._tunnel_host: - self.sock = sock - self._tunnel() - - server_hostname = self.host if ssl.HAS_SNI else None - self.sock = self._context.wrap_socket(sock, server_hostname=server_hostname) - try: - if self._check_hostname: - ssl.match_hostname(self.sock.getpeercert(), self.host) - except Exception: - self.sock.shutdown(socket.SHUT_RDWR) - self.sock.close() - raise - - __all__.append("HTTPSConnection") - - -class HTTPException(Exception): - # Subclasses that define an __init__ must call Exception.__init__ - # or define self.args. Otherwise, str() will fail. - pass - - -class NotConnected(HTTPException): - pass - - -class InvalidURL(HTTPException): - pass - - -class UnknownProtocol(HTTPException): - def __init__(self, version): - self.args = (version,) - self.version = version - - -class UnknownTransferEncoding(HTTPException): - pass - - -class UnimplementedFileMode(HTTPException): - pass - - -class IncompleteRead(HTTPException): - def __init__(self, partial, expected=None): - self.args = (partial,) - self.partial = partial - self.expected = expected - - def __repr__(self): - if self.expected is not None: - e = ", %i more expected" % self.expected - else: - e = "" - return "IncompleteRead(%i bytes read%s)" % (len(self.partial), e) - - def __str__(self): - return repr(self) - - -class ImproperConnectionState(HTTPException): - pass - - -class CannotSendRequest(ImproperConnectionState): - pass - - -class CannotSendHeader(ImproperConnectionState): - pass - - -class ResponseNotReady(ImproperConnectionState): - pass - - -class BadStatusLine(HTTPException): - def __init__(self, line): - if not line: - line = repr(line) - self.args = (line,) - self.line = line - - -class LineTooLong(HTTPException): - def __init__(self, line_type): - HTTPException.__init__( - self, "got more than %d bytes when reading %s" % (_MAXLINE, line_type) - ) - - -# for backwards compatibility -error = HTTPException diff --git a/modules/ulinksdk/_uhttp/example_client.py b/modules/ulinksdk/_uhttp/example_client.py deleted file mode 100644 index d556773066..0000000000 --- a/modules/ulinksdk/_uhttp/example_client.py +++ /dev/null @@ -1,9 +0,0 @@ -from http.client import HTTPConnection - - -conn = HTTPConnection("localhost") -# conn = HTTPConnection("python.org") -conn.request("GET", "/") -resp = conn.getresponse() -print(resp) -print(resp.read()) diff --git a/modules/ulinksdk/_umqtt/__init__.py b/modules/ulinksdk/_umqtt/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/modules/ulinksdk/_umqtt/errno.py b/modules/ulinksdk/_umqtt/errno.py deleted file mode 100644 index 0fff100d5a..0000000000 --- a/modules/ulinksdk/_umqtt/errno.py +++ /dev/null @@ -1,22 +0,0 @@ -EUNKNOWN=-1 -ECONCLOSE=1 -EREADLEN=2 -EWRITELEN=3 -ESTRTOLONG=4 -ERESPONSE=6 -EKEEPALIVE=7 -ENOCON=8 -ECONUNKNOWN=20 -ECONPROTOCOL=21 -ECONREJECT=22 -ECONUNAVAIBLE=23 -ECONCREDENTIALS=24 -ECONAUTH=25 -ECONNOT=28 -ECONLENGTH=29 -ECONTIMEOUT=30 -ESUBACKUNKNOWN=40 -ESUBACKFAIL=44 -STIMEOUT=0 -SDELIVERED=1 -SUNKNOWNPID=2 \ No newline at end of file diff --git a/modules/ulinksdk/_umqtt/robust2.py b/modules/ulinksdk/_umqtt/robust2.py deleted file mode 100644 index 37367cd047..0000000000 --- a/modules/ulinksdk/_umqtt/robust2.py +++ /dev/null @@ -1,121 +0,0 @@ -from utime import ticks_ms,ticks_diff -from . import simple2 -class MQTTClient(simple2.MQTTClient): - DEBUG=False;KEEP_QOS0=True;NO_QUEUE_DUPS=True;MSG_QUEUE_MAX=5;CONFIRM_QUEUE_MAX=10;RESUBSCRIBE=True - def __init__(A,*B,**C):super().__init__(*B,**C);A.subs=[];A.msg_to_send=[];A.sub_to_send=[];A.msg_to_confirm={};A.sub_to_confirm={};A.conn_issue=None - def is_keepalive(A): - B=ticks_diff(ticks_ms(),A.last_cpacket)//1000 - if 0=A.MSG_QUEUE_MAX: - E=min(map(lambda x:x[0]if x else 65535,A.msg_to_confirm.values()),default=0) - if 0A.CONFIRM_QUEUE_MAX:A.msg_to_confirm.pop(0) - return F - except (OSError,simple2.MQTTException)as G: - A.conn_issue=G,2 - if A.NO_QUEUE_DUPS: - if C in A.msg_to_send:return - if A.KEEP_QOS0 and B==0:A.add_msg_to_send(C) - elif B==1:A.add_msg_to_send(C) - def subscribe(A,topic,qos=0,resubscribe=True): - C=topic;B=C,qos - if A.RESUBSCRIBE and resubscribe: - if C not in dict(A.subs):A.subs.append(B) - A.sub_to_send[:]=[B for B in A.sub_to_send if C!=B[0]] - try: - D=super().subscribe(C,qos);A.sub_to_confirm.setdefault(B,[]).append(D) - if len(A.sub_to_confirm[B])>A.CONFIRM_QUEUE_MAX:A.sub_to_confirm.pop(0) - return D - except (OSError,simple2.MQTTException)as E: - A.conn_issue=E,3 - if A.NO_QUEUE_DUPS: - if B in A.sub_to_send:return - A.sub_to_send.append(B) - def send_queue(A): - D=[] - for B in A.msg_to_send: - E,I,J,C=B - try: - F=super().publish(E,I,J,C,False) - if C==1:A.msg_to_confirm.setdefault(B,[]).append(F) - D.append(B) - except (OSError,simple2.MQTTException)as G:A.conn_issue=G,5;return False - A.msg_to_send[:]=[B for B in A.msg_to_send if B not in D];del D;H=[] - for B in A.sub_to_send: - E,C=B - try:F=super().subscribe(E,C);A.sub_to_confirm.setdefault(B,[]).append(F);H.append(B) - except (OSError,simple2.MQTTException)as G:A.conn_issue=G,5;return False - A.sub_to_send[:]=[B for B in A.sub_to_send if B not in H];return True - def is_conn_issue(A): - A.is_keepalive() - if A.conn_issue:A.log() - return bool(A.conn_issue) - def wait_msg(A): - A.is_keepalive() - try:return super().wait_msg() - except (OSError,simple2.MQTTException)as B:A.conn_issue=B,8 - def check_msg(A): - A.is_keepalive() - try:return super().check_msg() - except (OSError,simple2.MQTTException)as B:A.conn_issue=B,10 \ No newline at end of file diff --git a/modules/ulinksdk/_umqtt/simple2.py b/modules/ulinksdk/_umqtt/simple2.py deleted file mode 100644 index e7fd46d072..0000000000 --- a/modules/ulinksdk/_umqtt/simple2.py +++ /dev/null @@ -1,126 +0,0 @@ -import usocket as socket -import uselect -from utime import ticks_add,ticks_ms,ticks_diff -class MQTTException(Exception):0 -def pid_gen(pid=0): - A=pid - while True:A=A+1 if A<65535 else 1;yield A -class MQTTClient: - def __init__(A,client_id,server,port=0,user=None,password=None,keepalive=0,ssl=False,ssl_params=None,socket_timeout=5,message_timeout=10): - C=ssl_params;B=port - if B==0:B=8883 if ssl else 1883 - A.client_id=client_id;A.sock=None;A.poller_r=None;A.poller_w=None;A.server=server;A.port=B;A.ssl=ssl;A.ssl_params=C if C else{};A.newpid=pid_gen() - if not getattr(A,'cb',None):A.cb=None - if not getattr(A,'cbstat',None):A.cbstat=lambda p,s:None - A.user=user;A.pswd=password;A.keepalive=keepalive;A.lw_topic=None;A.lw_msg=None;A.lw_qos=0;A.lw_retain=False;A.rcv_pids={};A.last_ping=ticks_ms();A.last_cpacket=ticks_ms();A.socket_timeout=socket_timeout;A.message_timeout=message_timeout - def _read(A,n): - try: - B=b'' - for C in range(n):A._sock_timeout(A.poller_r,A.socket_timeout);B+=A.sock.read(1) - except AttributeError:raise MQTTException(8) - if B==b'':raise MQTTException(1) - if len(B)!=n:raise MQTTException(2) - return B - def _write(A,bytes_wr,length=-1): - D=bytes_wr;B=length - try:A._sock_timeout(A.poller_w,A.socket_timeout);C=A.sock.write(D,B) - except AttributeError:raise MQTTException(8) - if B<0: - if C!=len(D):raise MQTTException(3) - elif C!=B:raise MQTTException(3) - return C - def _send_str(A,s):assert len(s)<65536;A._write(len(s).to_bytes(2,'big'));A._write(s) - def _recv_len(D): - A=0;B=0 - while 1: - C=D._read(1)[0];A|=(C&127)<127:buf[B]=A&127|128;A>>=7;B+=1 - buf[B]=A;return B+1 - def _sock_timeout(B,poller,socket_timeout): - A=socket_timeout - if B.sock: - C=poller.poll(-1 if A is None else int(A*1000)) - if not C:raise MQTTException(30) - else:raise MQTTException(28) - def set_callback(A,f):A.cb=f - def set_callback_status(A,f):A.cbstat=f - def set_last_will(A,topic,msg,retain=False,qos=0):B=topic;assert 0<=qos<=2;assert B;A.lw_topic=B;A.lw_msg=msg;A.lw_qos=qos;A.lw_retain=retain - def connect(A,clean_session=True): - E=clean_session;A.sock=socket.socket();G=socket.getaddrinfo(A.server,A.port)[0][-1];A.sock.connect(G) - if A.ssl:import ussl;A.sock=ussl.wrap_socket(A.sock,**A.ssl_params) - A.poller_r=uselect.poll();A.poller_r.register(A.sock,uselect.POLLIN);A.poller_w=uselect.poll();A.poller_w.register(A.sock,uselect.POLLOUT);F=bytearray(b'\x10\x00\x00\x00\x00\x00');B=bytearray(b'\x00\x04MQTT\x04\x00\x00\x00');D=10+2+len(A.client_id);B[7]=bool(E)<<1 - if bool(E):A.rcv_pids.clear() - if A.user is not None: - D+=2+len(A.user);B[7]|=1<<7 - if A.pswd is not None:D+=2+len(A.pswd);B[7]|=1<<6 - if A.keepalive:assert A.keepalive<65536;B[8]|=A.keepalive>>8;B[9]|=A.keepalive&255 - if A.lw_topic:D+=2+len(A.lw_topic)+2+len(A.lw_msg);B[7]|=4|(A.lw_qos&1)<<3|(A.lw_qos&2)<<3;B[7]|=A.lw_retain<<5 - H=A._varlen_encode(D,F,1);A._write(F,H);A._write(B);A._send_str(A.client_id) - if A.lw_topic:A._send_str(A.lw_topic);A._send_str(A.lw_msg) - if A.user is not None: - A._send_str(A.user) - if A.pswd is not None:A._send_str(A.pswd) - C=A._read(4) - if not(C[0]==32 and C[1]==2):raise MQTTException(29) - if C[3]!=0: - if 1<=C[3]<=5:raise MQTTException(20+C[3]) - else:raise MQTTException(20,C[3]) - A.last_cpacket=ticks_ms();return C[2]&1 - def disconnect(A):A._write(b'\xe0\x00');A.poller_r.unregister(A.sock);A.poller_w.unregister(A.sock);A.poller_r=None;A.poller_w=None;A.sock.close();A.sock=None - def ping(A):A._write(b'\xc0\x00');A.last_ping=ticks_ms() - def publish(A,topic,msg,retain=False,qos=0,dup=False): - E=topic;B=qos;assert B in(0,1);C=bytearray(b'0\x00\x00\x00\x00');C[0]|=B<<1|retain|int(dup)<<3;F=2+len(E)+len(msg) - if B>0:F+=2 - G=A._varlen_encode(F,C,1);A._write(C,G);A._send_str(E) - if B>0:D=next(A.newpid);A._write(D.to_bytes(2,'big')) - A._write(msg) - if B>0:A.rcv_pids[D]=ticks_add(ticks_ms(),A.message_timeout*1000);return D - - def subscribe(A,topic,qos=0):E=topic;assert qos in(0,1);assert A.cb is not None,'Subscribe callback is not set';B=bytearray(b'\x82\x00\x00\x00\x00\x00\x00');C=next(A.newpid);F=2+2+len(E)+1;D=A._varlen_encode(F,B,1);B[D:D+2]=C.to_bytes(2,'big');A._write(B,D+2);A._send_str(E);A._write(qos.to_bytes(1,'little'));A.rcv_pids[C]=ticks_add(ticks_ms(),A.message_timeout*1000);return C - def unsubscribe(A,topic,qos=0):E=topic;assert qos in(0,1);assert A.cb is not None,'Subscribe callback is not set';B=bytearray(b'\xA2\x00\x00\x00\x00\x00\x00');C=next(A.newpid);F=2+2+len(E);D=A._varlen_encode(F,B,1);B[D:D+2]=C.to_bytes(2,'big');A._write(B,D+2);A._send_str(E); A.rcv_pids[C]=ticks_add(ticks_ms(),A.message_timeout*1000);return C - - def _message_timeout(A): - C=ticks_ms() - for (B,D) in A.rcv_pids.items(): - if ticks_diff(D,C)<=0:A.rcv_pids.pop(B);A.cbstat(B,0) - def check_msg(A): - if A.sock: - if not A.poller_r.poll(-1 if A.socket_timeout is None else 1):A._message_timeout();return None - try: - G=A._read(1) - if not G:A._message_timeout();return None - except OSError as H: - if H.args[0]==110:A._message_timeout();return None - else:raise H - else:raise MQTTException(28) - if G==b'\xd0': - if A._read(1)[0]!=0:MQTTException(-1) - A.last_cpacket=ticks_ms();return - B=G[0] - if B==64: - D=A._read(1) - if D!=b'\x02':raise MQTTException(-1) - F=int.from_bytes(A._read(2),'big') - if F in A.rcv_pids:A.last_cpacket=ticks_ms();A.rcv_pids.pop(F);A.cbstat(F,1) - else:A.cbstat(F,2) - if B==144: - C=A._read(4) - if C[0]!=3:raise MQTTException(40,C) - if C[3]==128:raise MQTTException(44) - if C[3]not in(0,1,2):raise MQTTException(40,C) - E=C[2]|C[1]<<8 - if E in A.rcv_pids:A.last_cpacket=ticks_ms();A.rcv_pids.pop(E);A.cbstat(E,1) - else:raise MQTTException(5) - A._message_timeout() - if B&240!=48:return B - D=A._recv_len();I=int.from_bytes(A._read(2),'big');J=A._read(I);D-=I+2 - if B&6:E=int.from_bytes(A._read(2),'big');D-=2 - K=A._read(D)if D else b'';L=B&1;M=B&8;A.cb(J,K,bool(L),bool(M));A.last_cpacket=ticks_ms() - if B&6==2:A._write(b'@\x02');A._write(E.to_bytes(2,'big')) - elif B&6==4:raise NotImplementedError() - elif B&6==6:raise MQTTException(-1) - def wait_msg(A):B=A.socket_timeout;A.socket_timeout=None;C=A.check_msg();A.socket_timeout=B;return C \ No newline at end of file diff --git a/modules/ulinksdk/crc16.py b/modules/ulinksdk/crc16.py deleted file mode 100644 index fa73197da5..0000000000 --- a/modules/ulinksdk/crc16.py +++ /dev/null @@ -1,43 +0,0 @@ -crc_ibm_table = [ - 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, - 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, - 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, - 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, - 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, - 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, - 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, - 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, - 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, - 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, - 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, - 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, - 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, - 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, - 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, - 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, - 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, - 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, - 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, - 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, - 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, - 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, - 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, - 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, - 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, - 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, - 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, - 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, - 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, - 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, - 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, - 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, -] - -def crc_ibm (data): - global crc_ibm_table - crc = 0x0000 - lut = 0 - for byte in data: - lut = (crc ^ (byte)) & 0xFF - crc = (crc >> 8) ^ crc_ibm_table[lut] - return crc \ No newline at end of file diff --git a/modules/ulinksdk/dynreg.py b/modules/ulinksdk/dynreg.py index fe8517215d..3d503f714f 100644 --- a/modules/ulinksdk/dynreg.py +++ b/modules/ulinksdk/dynreg.py @@ -1,11 +1,10 @@ -#from _uhttp.client import HTTPConnection -import urequests as requests +import usocket import _thread import urandom as random import json import hmac -from hashlib._sha256 import sha256 -import http +import uos +import utime class DYNREG: STATE_USER_INPUT_NULL_POINTER = (-0x0101) @@ -14,126 +13,154 @@ class DYNREG: STATE_USER_INPUT_MISSING_PRODUCT_SECRET = (-0x0107) STATE_USER_INPUT_MISSING_DEVICE_NAME = (-0x0105) STATE_USER_INPUT_EXEC_DISABLED = (-0x0109) + CORE_HTTP_DEFAULT_HEADER_LINE_MAX_LEN = 128 + + def __init__(self): + self.callback = None + self.keepAlive = 300 + self.region = None + self.product_key = None + self.product_secret = None + self.device_name = None + self.device_Secret = None + self.register_success = False + self.clean_session = False + self.clientID = '13225554099' # 自定义字符(不超过64) + self.connected = False + self.http_client = None + self.msg_data = None + self.port = 443 - - def __init__(self, lk_handle = None, deviceinfo = None, cb = None): + def __save_Secret(self, data): + secret_data = {} + if "secret.json" in uos.listdir("/"): + with open("/secret.json", "r", encoding="utf-8") as f: + secret_data = json.load(f) + try: + with open("/secret.json", "w+", encoding="utf-8") as w: + secret_data.update(data) + json.dump(secret_data, w) + except Exception as e: + print("[ERROR] File write failed : %s" % str(e)) + + def dynreg_rundom(self): + msg = "" + for i in range(0,5): + num = random.randint(1, 10) + msg += str(num) + return msg + + def register(self, deviceinfo, cb): # param check - print("params check") - if 'region' not in deviceinfo: - raise ValueError("region wrong!") if 'productKey' not in deviceinfo: raise ValueError("product key wrong!") if 'deviceName' not in deviceinfo: raise ValueError("device name wrong!") if 'productSecret' not in deviceinfo: raise ValueError("device secret wrong!") - print("params done") - self.lk_handle = lk_handle - - self.region = deviceinfo['region'] - self.host = "iot-auth.{}.aliyuncs.com".format(self.region) - self.port = 443 - + self.region = "cn-shanghai" + if 'region' in deviceinfo: + self.region = deviceinfo['region'] self.product_key = deviceinfo['productKey'] self.product_secret = deviceinfo['productSecret'] self.device_name = deviceinfo['deviceName'] - + self.host = "iot-auth.{}.aliyuncs.com".format(self.region) + self.port = 443 self.callback = cb - - self.recv_handler = self.dynreg_recv_handler - self.http_conn = None - - def dynreg_recv_handler(self): - pass - - def dynreg_send_request(self): - - ### test - - '''data_patch = { - 'url': 'http://httpbin.org/post', - 'method': 'POST', - 'headers': { - 'Content-Type':'application/x-www-form-urlencoded', - 'Accept': 'text/xml,text/javascript,text/html,application/json', - 'Content-Length': '145', - }, - 'timeout': 6000, - 'params': '' - } - - http.request(data_patch, self.callback)''' - - ### test - - print('start to send http request...') - '''res = 0 - #self.http_conn = HTTPConnection(self.host, self.port) - - req_method = "POST" - req_path = "/auth/register/device" - - random_str = str(int(random.random()*10000)) - print("random_str:", random_str) - - hmac_msg = "deviceName{}productKey{}random{}".format(self.device_name, self.product_key, random_str) - - - sign_str = hmac.new(bytes(self.product_secret, "utf8"), msg=bytes(hmac_msg, "utf8"), digestmod=sha256).hexdigest() - print("sign_str:", sign_str) - - req_content = "productKey={}&deviceName={}&random={}&sign={}&signMethod=hmacsha256".format(self.product_key, self.device_name, random_str, sign_str) - - - req_header_dic= dict() - #req_header_dic['Host'] = self.host - req_header_dic['Accept'] = "text/xml,text/javascript,text/html,application/json" - req_header_dic['Content-Type'] = "application/x-www-form-urlencoded" - req_header_dic['Content-Length'] = str(len(req_content)) - - request_url = self.host + req_path - - print("\n\nreq_method:",req_method) - print("request_url:",request_url) - print("req_content:", req_content) - print("req_header:", req_header_dic) - print('\n\n') - - print('send http request start...') - - res = requests.post(request_url, headers=req_header_dic, data = req_content) - print("post done") - print(res)''' - - - # requests.request(method=req_method, url=request_url, data= req_content.encode('utf-8'), headers=req_header_dic) - - ######### test use - request_url = "http://httpbin.org/post" - req_content = json.dumps({ "cid": 1, "arg": 2}) - req_header_dic = { 'Content-Type':'application/x-www-form-urlencoded', 'Accept': 'text/xml,text/javascript,text/html,application/json'} - ######### test use - res = requests.post(request_url, headers=req_header_dic, data = req_content) - print('send http request finished...') - print(res) - - - '''combine_header = "{} {} HTTP/1.1\r\nHost: {}\r\n{}Content-Length: {}\r\n\r\n".format(req_method, req_path, self.host, \ - json.dumps(req_header_dic), str(len(req_content)))''' - - - def dynreg_recv(self): - pass - '''print('start to receive message ...') - if self.http_conn == None: + self.username = "{}&{}".format(self.device_name, self.product_key) + # print("self.username",self.username) + # print("[INFO] dynamic registration") + + self.dynreg_connect() + self.dynreg_send_req() + status_code, content = self.dynreg_receive() + # print("status_code, content", status_code, content) + # print("msg['code']",msg['code']) + if (status_code != 200): + self.register_success = False + else: + self.msg_data = msg = json.loads(content) + if (msg['code'] == 200): + self.register_success = True + data = {msg['data']['deviceName']: msg['data']['deviceSecret']} + self.__save_Secret(data) + elif (msg['code'] == 6289): + self.register_success = True + else: + self.register_success = False + + if (self.register_success == True): + #print('register success!') + self.callback(self.msg_data) + return 0 + else: return -1 - res = 0 - - resp = self.http_conn.getresponse() - print(resp) - print(resp.read())''' - - - - \ No newline at end of file + def dynreg_sign (self,randomNum): + plain_text = "deviceName%sproductKey%srandom%s" % (self.device_name, self.product_key, randomNum) + #sign_hex = _hmac.new(bytes(self.product_secret, "utf8"), msg=bytes(plain_text, "utf8"), digestmod=sha256).hexdigest() + sign_hex = hmac.sha256(self.product_secret, plain_text) + return sign_hex + + def dynreg_connect(self): + self.sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM) + sockaddr = usocket.getaddrinfo(self.host, self.port)[0][-1] + #sockaddr = usocket.getaddrinfo("www.baidu.com", self.port)[0][-1] + self.sock.connect(sockaddr) + import ussl + #self.sock = ussl.wrap_socket(self.sock, server_side = False, server_hostname = self.host, key = None, cert = cred['x509_server_cert'].encode('utf-8')) + self.sock = ussl.wrap_socket(self.sock) + def dynreg_send_req(self): + randomNum = self.dynreg_rundom() + sign_str = self.dynreg_sign(randomNum) + content = "productKey=%s&deviceName=%s&random=%s&sign=%s&signMethod=hmacsha256" % \ + (self.product_key, self.device_name, randomNum, sign_str) + request = { + "method": "POST", + "path": "/auth/register/device", + "header" : "Accept: text/xml,text/javascript,text/html,application/json\r\nContent-Type: application/x-www-form-urlencoded\r\n", + "content" : content, + "content_len" : len(content) + } + combine_header = "%s %s HTTP/1.1\r\nHost: %s\r\n%sContent-Length: %s\r\n\r\n" % \ + (request["method"], request["path"], self.host, request["header"], request["content_len"]) + + # send header + #print("combine_header:\n", combine_header) # test point + self.sock.write(combine_header) + + #print("content:\n", content) # test point + # send body + self.sock.write(content) + + + def dynreg_receive(self): + # receive header + status_code = 0 + content_length = 0 + line = str() + while True: + char = self.sock.read(1).decode() + if (char != '\r'): + line = line + char + else: + #print(line) + if (line[0:8] == 'HTTP/1.1'): + status_code = int(line.split(' ')[1]) + if (line[0:14] == 'Content-Length'): + content_length = int(line.split(' ')[1]) + + self.sock.read(1).decode() # '\n' + + line = str() + char = self.sock.read(1).decode() + if(char == '\r'): + self.sock.read(1).decode() # '\n' + break + line = line + char + + #print('status_code:', status_code) + #print('content_length:', content_length) + content = self.sock.read(content_length).decode() + #print(content) + return status_code, content diff --git a/modules/ulinksdk/email/__init__.py b/modules/ulinksdk/email/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/modules/ulinksdk/email/errors.py b/modules/ulinksdk/email/errors.py deleted file mode 100644 index 4c76f6f7d5..0000000000 --- a/modules/ulinksdk/email/errors.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright (C) 2001-2006 Python Software Foundation -# Author: Barry Warsaw -# Contact: email-sig@python.org - -"""email package exception classes.""" - - -class MessageError(Exception): - """Base class for errors in the email package.""" - - -class MessageParseError(MessageError): - """Base class for message parsing errors.""" - - -class HeaderParseError(MessageParseError): - """Error while parsing headers.""" - - -class BoundaryError(MessageParseError): - """Couldn't find terminating boundary.""" - - -class MultipartConversionError(MessageError): # , TypeError): - """Conversion to a multipart is prohibited.""" - - -class CharsetError(MessageError): - """An illegal charset was given.""" - - -# These are parsing defects which the parser was able to work around. -class MessageDefect(ValueError): - """Base class for a message defect.""" - - def __init__(self, line=None): - if line is not None: - super().__init__(line) - self.line = line - - -class NoBoundaryInMultipartDefect(MessageDefect): - """A message claimed to be a multipart but had no boundary parameter.""" - - -class StartBoundaryNotFoundDefect(MessageDefect): - """The claimed start boundary was never found.""" - - -class CloseBoundaryNotFoundDefect(MessageDefect): - """A start boundary was found, but not the corresponding close boundary.""" - - -class FirstHeaderLineIsContinuationDefect(MessageDefect): - """A message had a continuation line as its first header line.""" - - -class MisplacedEnvelopeHeaderDefect(MessageDefect): - """A 'Unix-from' header was found in the middle of a header block.""" - - -class MissingHeaderBodySeparatorDefect(MessageDefect): - """Found line with no leading whitespace and no colon before blank line.""" - - -# XXX: backward compatibility, just in case (it was never emitted). -MalformedHeaderDefect = MissingHeaderBodySeparatorDefect - - -class MultipartInvariantViolationDefect(MessageDefect): - """A message claimed to be a multipart but no subparts were found.""" - - -class InvalidMultipartContentTransferEncodingDefect(MessageDefect): - """An invalid content transfer encoding was set on the multipart itself.""" - - -class UndecodableBytesDefect(MessageDefect): - """Header contained bytes that could not be decoded""" - - -class InvalidBase64PaddingDefect(MessageDefect): - """base64 encoded sequence had an incorrect length""" - - -class InvalidBase64CharactersDefect(MessageDefect): - """base64 encoded sequence had characters not in base64 alphabet""" - - -# These errors are specific to header parsing. - - -class HeaderDefect(MessageDefect): - """Base class for a header defect.""" - - def __init__(self, *args, **kw): - super().__init__(*args, **kw) - - -class InvalidHeaderDefect(HeaderDefect): - """Header is not valid, message gives details.""" - - -class HeaderMissingRequiredValue(HeaderDefect): - """A header that must have a value had none""" - - -class NonPrintableDefect(HeaderDefect): - """ASCII characters outside the ascii-printable range found""" - - def __init__(self, non_printables): - super().__init__(non_printables) - self.non_printables = non_printables - - def __str__(self): - return "the following ASCII non-printables found in header: " "{}".format( - self.non_printables - ) - - -class ObsoleteHeaderDefect(HeaderDefect): - """Header uses syntax declared obsolete by RFC 5322""" - - -class NonASCIILocalPartDefect(HeaderDefect): - """local_part contains non-ASCII characters""" - - # This defect only occurs during unicode parsing, not when - # parsing messages decoded from binary. diff --git a/modules/ulinksdk/email/feedparser.py b/modules/ulinksdk/email/feedparser.py deleted file mode 100644 index 15f7fffa7a..0000000000 --- a/modules/ulinksdk/email/feedparser.py +++ /dev/null @@ -1,519 +0,0 @@ -# Copyright (C) 2004-2006 Python Software Foundation -# Authors: Baxter, Wouters and Warsaw -# Contact: email-sig@python.org - -"""FeedParser - An email feed parser. - -The feed parser implements an interface for incrementally parsing an email -message, line by line. This has advantages for certain applications, such as -those reading email messages off a socket. - -FeedParser.feed() is the primary interface for pushing new data into the -parser. It returns when there's nothing more it can do with the available -data. When you have no more data to push into the parser, call .close(). -This completes the parsing and returns the root message object. - -The other advantage of this parser is that it will never raise a parsing -exception. Instead, when it finds something unexpected, it adds a 'defect' to -the current message. Defects are just instances that live on the message -object's .defects attribute. -""" - -__all__ = ["FeedParser", "BytesFeedParser"] - -import re - -from email import errors -from email import message -from email._policybase import compat32 - -NLCRE = re.compile("\r\n|\r|\n") -NLCRE_bol = re.compile("(\r\n|\r|\n)") -NLCRE_eol = re.compile("(\r\n|\r|\n)\Z") -NLCRE_crack = re.compile("(\r\n|\r|\n)") -# RFC 2822 $3.6.8 Optional fields. ftext is %d33-57 / %d59-126, Any character -# except controls, SP, and ":". -headerRE = re.compile(r"^(From |[\041-\071\073-\176]{1,}:|[\t ])") -EMPTYSTRING = "" -NL = "\n" - -NeedMoreData = object() - - -class BufferedSubFile(object): - """A file-ish object that can have new data loaded into it. - - You can also push and pop line-matching predicates onto a stack. When the - current predicate matches the current line, a false EOF response - (i.e. empty string) is returned instead. This lets the parser adhere to a - simple abstraction -- it parses until EOF closes the current message. - """ - - def __init__(self): - # The last partial line pushed into this object. - self._partial = "" - # The list of full, pushed lines, in reverse order - self._lines = [] - # The stack of false-EOF checking predicates. - self._eofstack = [] - # A flag indicating whether the file has been closed or not. - self._closed = False - - def push_eof_matcher(self, pred): - self._eofstack.append(pred) - - def pop_eof_matcher(self): - return self._eofstack.pop() - - def close(self): - # Don't forget any trailing partial line. - self._lines.append(self._partial) - self._partial = "" - self._closed = True - - def readline(self): - if not self._lines: - if self._closed: - return "" - return NeedMoreData - # Pop the line off the stack and see if it matches the current - # false-EOF predicate. - line = self._lines.pop() - # RFC 2046, section 5.1.2 requires us to recognize outer level - # boundaries at any level of inner nesting. Do this, but be sure it's - # in the order of most to least nested. - for ateof in self._eofstack[::-1]: - if ateof(line): - # We're at the false EOF. But push the last line back first. - self._lines.append(line) - return "" - return line - - def unreadline(self, line): - # Let the consumer push a line back into the buffer. - assert line is not NeedMoreData - self._lines.append(line) - - def push(self, data): - """Push some new data into this object.""" - # Handle any previous leftovers - data, self._partial = self._partial + data, "" - # Crack into lines, but preserve the newlines on the end of each - parts = NLCRE_crack.split(data) - # The *ahem* interesting behaviour of re.split when supplied grouping - # parentheses is that the last element of the resulting list is the - # data after the final RE. In the case of a NL/CR terminated string, - # this is the empty string. - self._partial = parts.pop() - # GAN 29Mar09 bugs 1555570, 1721862 Confusion at 8K boundary ending with \r: - # is there a \n to follow later? - if not self._partial and parts and parts[-1].endswith("\r"): - self._partial = parts.pop(-2) + parts.pop() - # parts is a list of strings, alternating between the line contents - # and the eol character(s). Gather up a list of lines after - # re-attaching the newlines. - lines = [] - for i in range(len(parts) // 2): - lines.append(parts[i * 2] + parts[i * 2 + 1]) - self.pushlines(lines) - - def pushlines(self, lines): - # Reverse and insert at the front of the lines. - self._lines[:0] = lines[::-1] - - def __iter__(self): - return self - - def __next__(self): - line = self.readline() - if line == "": - raise StopIteration - return line - - -class FeedParser: - """A feed-style parser of email.""" - - def __init__(self, _factory=message.Message, policy=compat32): - """_factory is called with no arguments to create a new message obj - - The policy keyword specifies a policy object that controls a number of - aspects of the parser's operation. The default policy maintains - backward compatibility. - - """ - self._factory = _factory - self.policy = policy - try: - _factory(policy=self.policy) - self._factory_kwds = lambda: {"policy": self.policy} - except TypeError: - # Assume this is an old-style factory - self._factory_kwds = lambda: {} - self._input = BufferedSubFile() - self._msgstack = [] - self._parse = self._parsegen().__next__ - self._cur = None - self._last = None - self._headersonly = False - - # Non-public interface for supporting Parser's headersonly flag - def _set_headersonly(self): - self._headersonly = True - - def feed(self, data): - """Push more data into the parser.""" - self._input.push(data) - self._call_parse() - - def _call_parse(self): - try: - self._parse() - except StopIteration: - pass - - def close(self): - """Parse all remaining data and return the root message object.""" - self._input.close() - self._call_parse() - root = self._pop_message() - assert not self._msgstack - # Look for final set of defects - if root.get_content_maintype() == "multipart" and not root.is_multipart(): - defect = errors.MultipartInvariantViolationDefect() - self.policy.handle_defect(root, defect) - return root - - def _new_message(self): - msg = self._factory(**self._factory_kwds()) - if self._cur and self._cur.get_content_type() == "multipart/digest": - msg.set_default_type("message/rfc822") - if self._msgstack: - self._msgstack[-1].attach(msg) - self._msgstack.append(msg) - self._cur = msg - self._last = msg - - def _pop_message(self): - retval = self._msgstack.pop() - if self._msgstack: - self._cur = self._msgstack[-1] - else: - self._cur = None - return retval - - def _parsegen(self): - # Create a new message and start by parsing headers. - self._new_message() - headers = [] - # Collect the headers, searching for a line that doesn't match the RFC - # 2822 header or continuation pattern (including an empty line). - for line in self._input: - if line is NeedMoreData: - yield NeedMoreData - continue - if not headerRE.match(line): - # If we saw the RFC defined header/body separator - # (i.e. newline), just throw it away. Otherwise the line is - # part of the body so push it back. - if not NLCRE.match(line): - defect = errors.MissingHeaderBodySeparatorDefect() - self.policy.handle_defect(self._cur, defect) - self._input.unreadline(line) - break - headers.append(line) - # Done with the headers, so parse them and figure out what we're - # supposed to see in the body of the message. - self._parse_headers(headers) - # Headers-only parsing is a backwards compatibility hack, which was - # necessary in the older parser, which could raise errors. All - # remaining lines in the input are thrown into the message body. - if self._headersonly: - lines = [] - while True: - line = self._input.readline() - if line is NeedMoreData: - yield NeedMoreData - continue - if line == "": - break - lines.append(line) - self._cur.set_payload(EMPTYSTRING.join(lines)) - return - if self._cur.get_content_type() == "message/delivery-status": - # message/delivery-status contains blocks of headers separated by - # a blank line. We'll represent each header block as a separate - # nested message object, but the processing is a bit different - # than standard message/* types because there is no body for the - # nested messages. A blank line separates the subparts. - while True: - self._input.push_eof_matcher(NLCRE.match) - for retval in self._parsegen(): - if retval is NeedMoreData: - yield NeedMoreData - continue - break - msg = self._pop_message() - # We need to pop the EOF matcher in order to tell if we're at - # the end of the current file, not the end of the last block - # of message headers. - self._input.pop_eof_matcher() - # The input stream must be sitting at the newline or at the - # EOF. We want to see if we're at the end of this subpart, so - # first consume the blank line, then test the next line to see - # if we're at this subpart's EOF. - while True: - line = self._input.readline() - if line is NeedMoreData: - yield NeedMoreData - continue - break - while True: - line = self._input.readline() - if line is NeedMoreData: - yield NeedMoreData - continue - break - if line == "": - break - # Not at EOF so this is a line we're going to need. - self._input.unreadline(line) - return - if self._cur.get_content_maintype() == "message": - # The message claims to be a message/* type, then what follows is - # another RFC 2822 message. - for retval in self._parsegen(): - if retval is NeedMoreData: - yield NeedMoreData - continue - break - self._pop_message() - return - if self._cur.get_content_maintype() == "multipart": - boundary = self._cur.get_boundary() - if boundary is None: - # The message /claims/ to be a multipart but it has not - # defined a boundary. That's a problem which we'll handle by - # reading everything until the EOF and marking the message as - # defective. - defect = errors.NoBoundaryInMultipartDefect() - self.policy.handle_defect(self._cur, defect) - lines = [] - for line in self._input: - if line is NeedMoreData: - yield NeedMoreData - continue - lines.append(line) - self._cur.set_payload(EMPTYSTRING.join(lines)) - return - # Make sure a valid content type was specified per RFC 2045:6.4. - if self._cur.get("content-transfer-encoding", "8bit").lower() not in ( - "7bit", - "8bit", - "binary", - ): - defect = errors.InvalidMultipartContentTransferEncodingDefect() - self.policy.handle_defect(self._cur, defect) - # Create a line match predicate which matches the inter-part - # boundary as well as the end-of-multipart boundary. Don't push - # this onto the input stream until we've scanned past the - # preamble. - separator = "--" + boundary - boundaryre = re.compile( - "(?P" - + re.escape(separator) - + r")(?P--)?(?P[ \t]*)(?P\r\n|\r|\n)?$" - ) - capturing_preamble = True - preamble = [] - linesep = False - close_boundary_seen = False - while True: - line = self._input.readline() - if line is NeedMoreData: - yield NeedMoreData - continue - if line == "": - break - mo = boundaryre.match(line) - if mo: - # If we're looking at the end boundary, we're done with - # this multipart. If there was a newline at the end of - # the closing boundary, then we need to initialize the - # epilogue with the empty string (see below). - if mo.group("end"): - close_boundary_seen = True - linesep = mo.group("linesep") - break - # We saw an inter-part boundary. Were we in the preamble? - if capturing_preamble: - if preamble: - # According to RFC 2046, the last newline belongs - # to the boundary. - lastline = preamble[-1] - eolmo = NLCRE_eol.search(lastline) - if eolmo: - preamble[-1] = lastline[: -len(eolmo.group(0))] - self._cur.preamble = EMPTYSTRING.join(preamble) - capturing_preamble = False - self._input.unreadline(line) - continue - # We saw a boundary separating two parts. Consume any - # multiple boundary lines that may be following. Our - # interpretation of RFC 2046 BNF grammar does not produce - # body parts within such double boundaries. - while True: - line = self._input.readline() - if line is NeedMoreData: - yield NeedMoreData - continue - mo = boundaryre.match(line) - if not mo: - self._input.unreadline(line) - break - # Recurse to parse this subpart; the input stream points - # at the subpart's first line. - self._input.push_eof_matcher(boundaryre.match) - for retval in self._parsegen(): - if retval is NeedMoreData: - yield NeedMoreData - continue - break - # Because of RFC 2046, the newline preceding the boundary - # separator actually belongs to the boundary, not the - # previous subpart's payload (or epilogue if the previous - # part is a multipart). - if self._last.get_content_maintype() == "multipart": - epilogue = self._last.epilogue - if epilogue == "": - self._last.epilogue = None - elif epilogue is not None: - mo = NLCRE_eol.search(epilogue) - if mo: - end = len(mo.group(0)) - self._last.epilogue = epilogue[:-end] - else: - payload = self._last._payload - if isinstance(payload, str): - mo = NLCRE_eol.search(payload) - if mo: - payload = payload[: -len(mo.group(0))] - self._last._payload = payload - self._input.pop_eof_matcher() - self._pop_message() - # Set the multipart up for newline cleansing, which will - # happen if we're in a nested multipart. - self._last = self._cur - else: - # I think we must be in the preamble - assert capturing_preamble - preamble.append(line) - # We've seen either the EOF or the end boundary. If we're still - # capturing the preamble, we never saw the start boundary. Note - # that as a defect and store the captured text as the payload. - if capturing_preamble: - defect = errors.StartBoundaryNotFoundDefect() - self.policy.handle_defect(self._cur, defect) - self._cur.set_payload(EMPTYSTRING.join(preamble)) - epilogue = [] - for line in self._input: - if line is NeedMoreData: - yield NeedMoreData - continue - self._cur.epilogue = EMPTYSTRING.join(epilogue) - return - # If we're not processing the preamble, then we might have seen - # EOF without seeing that end boundary...that is also a defect. - if not close_boundary_seen: - defect = errors.CloseBoundaryNotFoundDefect() - self.policy.handle_defect(self._cur, defect) - return - # Everything from here to the EOF is epilogue. If the end boundary - # ended in a newline, we'll need to make sure the epilogue isn't - # None - if linesep: - epilogue = [""] - else: - epilogue = [] - for line in self._input: - if line is NeedMoreData: - yield NeedMoreData - continue - epilogue.append(line) - # Any CRLF at the front of the epilogue is not technically part of - # the epilogue. Also, watch out for an empty string epilogue, - # which means a single newline. - if epilogue: - firstline = epilogue[0] - bolmo = NLCRE_bol.match(firstline) - if bolmo: - epilogue[0] = firstline[len(bolmo.group(0)) :] - self._cur.epilogue = EMPTYSTRING.join(epilogue) - return - # Otherwise, it's some non-multipart type, so the entire rest of the - # file contents becomes the payload. - lines = [] - for line in self._input: - if line is NeedMoreData: - yield NeedMoreData - continue - lines.append(line) - self._cur.set_payload(EMPTYSTRING.join(lines)) - - def _parse_headers(self, lines): - # Passed a list of lines that make up the headers for the current msg - lastheader = "" - lastvalue = [] - for lineno, line in enumerate(lines): - # Check for continuation - if line[0] in " \t": - if not lastheader: - # The first line of the headers was a continuation. This - # is illegal, so let's note the defect, store the illegal - # line, and ignore it for purposes of headers. - defect = errors.FirstHeaderLineIsContinuationDefect(line) - self.policy.handle_defect(self._cur, defect) - continue - lastvalue.append(line) - continue - if lastheader: - self._cur.set_raw(*self.policy.header_source_parse(lastvalue)) - lastheader, lastvalue = "", [] - # Check for envelope header, i.e. unix-from - if line.startswith("From "): - if lineno == 0: - # Strip off the trailing newline - mo = NLCRE_eol.search(line) - if mo: - line = line[: -len(mo.group(0))] - self._cur.set_unixfrom(line) - continue - elif lineno == len(lines) - 1: - # Something looking like a unix-from at the end - it's - # probably the first line of the body, so push back the - # line and stop. - self._input.unreadline(line) - return - else: - # Weirdly placed unix-from line. Note this as a defect - # and ignore it. - defect = errors.MisplacedEnvelopeHeaderDefect(line) - self._cur.defects.append(defect) - continue - # Split the line on the colon separating field name from value. - # There will always be a colon, because if there wasn't the part of - # the parser that calls us would have started parsing the body. - i = line.find(":") - assert i > 0, "_parse_headers fed line with no : and no leading WS" - lastheader = line[:i] - lastvalue = [line] - # Done with all the lines, so handle the last header. - if lastheader: - self._cur.set_raw(*self.policy.header_source_parse(lastvalue)) - - -class BytesFeedParser(FeedParser): - """Like FeedParser, but feed accepts bytes.""" - - def feed(self, data): - super().feed(data.decode("ascii", "surrogateescape")) diff --git a/modules/ulinksdk/email/iterators.py b/modules/ulinksdk/email/iterators.py deleted file mode 100644 index e15cd6f6c2..0000000000 --- a/modules/ulinksdk/email/iterators.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (C) 2001-2006 Python Software Foundation -# Author: Barry Warsaw -# Contact: email-sig@python.org - -"""Various types of useful iterators and generators.""" - -__all__ = [ - "body_line_iterator", - "typed_subpart_iterator", - "walk", - # Do not include _structure() since it's part of the debugging API. -] - -import sys -from io import StringIO - - -# This function will become a method of the Message class -def walk(self): - """Walk over the message tree, yielding each subpart. - - The walk is performed in depth-first order. This method is a - generator. - """ - yield self - if self.is_multipart(): - for subpart in self.get_payload(): - for subsubpart in subpart.walk(): - yield subsubpart - - -# These two functions are imported into the Iterators.py interface module. -def body_line_iterator(msg, decode=False): - """Iterate over the parts, returning string payloads line-by-line. - - Optional decode (default False) is passed through to .get_payload(). - """ - for subpart in msg.walk(): - payload = subpart.get_payload(decode=decode) - if isinstance(payload, str): - for line in StringIO(payload): - yield line - - -def typed_subpart_iterator(msg, maintype="text", subtype=None): - """Iterate over the subparts with a given MIME type. - - Use `maintype' as the main MIME type to match against; this defaults to - "text". Optional `subtype' is the MIME subtype to match against; if - omitted, only the main type is matched. - """ - for subpart in msg.walk(): - if subpart.get_content_maintype() == maintype: - if subtype is None or subpart.get_content_subtype() == subtype: - yield subpart - - -def _structure(msg, fp=None, level=0, include_default=False): - """A handy debugging aid""" - if fp is None: - fp = sys.stdout - tab = " " * (level * 4) - print(tab + msg.get_content_type(), end="", file=fp) - if include_default: - print(" [%s]" % msg.get_default_type(), file=fp) - else: - print(file=fp) - if msg.is_multipart(): - for subpart in msg.get_payload(): - _structure(subpart, fp, level + 1, include_default) diff --git a/modules/ulinksdk/email/message.py b/modules/ulinksdk/email/message.py deleted file mode 100644 index b669e5c1aa..0000000000 --- a/modules/ulinksdk/email/message.py +++ /dev/null @@ -1,878 +0,0 @@ -# Copyright (C) 2001-2007 Python Software Foundation -# Author: Barry Warsaw -# Contact: email-sig@python.org - -"""Basic message object for the email package object model.""" - -__all__ = ["Message"] - -import re -import uu -import base64 -import binascii -from io import BytesIO, StringIO - -# Intrapackage imports -from email import utils -from email import errors -from email._policybase import compat32 -from email import charset as _charset -from email._encoded_words import decode_b - -Charset = _charset.Charset - -SEMISPACE = "; " - -# Regular expression that matches `special' characters in parameters, the -# existence of which force quoting of the parameter value. -tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') - - -def _splitparam(param): - # Split header parameters. BAW: this may be too simple. It isn't - # strictly RFC 2045 (section 5.1) compliant, but it catches most headers - # found in the wild. We may eventually need a full fledged parser. - # RDM: we might have a Header here; for now just stringify it. - a, sep, b = str(param).partition(";") - if not sep: - return a.strip(), None - return a.strip(), b.strip() - - -def _formatparam(param, value=None, quote=True): - """Convenience function to format and return a key=value pair. - - This will quote the value if needed or if quote is true. If value is a - three tuple (charset, language, value), it will be encoded according - to RFC2231 rules. If it contains non-ascii characters it will likewise - be encoded according to RFC2231 rules, using the utf-8 charset and - a null language. - """ - if value is not None and len(value) > 0: - # A tuple is used for RFC 2231 encoded parameter values where items - # are (charset, language, value). charset is a string, not a Charset - # instance. RFC 2231 encoded values are never quoted, per RFC. - if isinstance(value, tuple): - # Encode as per RFC 2231 - param += "*" - value = utils.encode_rfc2231(value[2], value[0], value[1]) - return "%s=%s" % (param, value) - else: - try: - value.encode("ascii") - except UnicodeEncodeError: - param += "*" - value = utils.encode_rfc2231(value, "utf-8", "") - return "%s=%s" % (param, value) - # BAW: Please check this. I think that if quote is set it should - # force quoting even if not necessary. - if quote or tspecials.search(value): - return '%s="%s"' % (param, utils.quote(value)) - else: - return "%s=%s" % (param, value) - else: - return param - - -def _parseparam(s): - # RDM This might be a Header, so for now stringify it. - s = ";" + str(s) - plist = [] - while s[:1] == ";": - s = s[1:] - end = s.find(";") - while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2: - end = s.find(";", end + 1) - if end < 0: - end = len(s) - f = s[:end] - if "=" in f: - i = f.index("=") - f = f[:i].strip().lower() + "=" + f[i + 1 :].strip() - plist.append(f.strip()) - s = s[end:] - return plist - - -def _unquotevalue(value): - # This is different than utils.collapse_rfc2231_value() because it doesn't - # try to convert the value to a unicode. Message.get_param() and - # Message.get_params() are both currently defined to return the tuple in - # the face of RFC 2231 parameters. - if isinstance(value, tuple): - return value[0], value[1], utils.unquote(value[2]) - else: - return utils.unquote(value) - - -class Message: - """Basic message object. - - A message object is defined as something that has a bunch of RFC 2822 - headers and a payload. It may optionally have an envelope header - (a.k.a. Unix-From or From_ header). If the message is a container (i.e. a - multipart or a message/rfc822), then the payload is a list of Message - objects, otherwise it is a string. - - Message objects implement part of the `mapping' interface, which assumes - there is exactly one occurrence of the header per message. Some headers - do in fact appear multiple times (e.g. Received) and for those headers, - you must use the explicit API to set or get all the headers. Not all of - the mapping methods are implemented. - """ - - def __init__(self, policy=compat32): - self.policy = policy - self._headers = [] - self._unixfrom = None - self._payload = None - self._charset = None - # Defaults for multipart messages - self.preamble = self.epilogue = None - self.defects = [] - # Default content type - self._default_type = "text/plain" - - def __str__(self): - """Return the entire formatted message as a string. - This includes the headers, body, and envelope header. - """ - return self.as_string() - - def as_string(self, unixfrom=False, maxheaderlen=0): - """Return the entire formatted message as a string. - Optional `unixfrom' when True, means include the Unix From_ envelope - header. - - This is a convenience method and may not generate the message exactly - as you intend. For more flexibility, use the flatten() method of a - Generator instance. - """ - from email.generator import Generator - - fp = StringIO() - g = Generator(fp, mangle_from_=False, maxheaderlen=maxheaderlen) - g.flatten(self, unixfrom=unixfrom) - return fp.getvalue() - - def is_multipart(self): - """Return True if the message consists of multiple parts.""" - return isinstance(self._payload, list) - - # - # Unix From_ line - # - def set_unixfrom(self, unixfrom): - self._unixfrom = unixfrom - - def get_unixfrom(self): - return self._unixfrom - - # - # Payload manipulation. - # - def attach(self, payload): - """Add the given payload to the current payload. - - The current payload will always be a list of objects after this method - is called. If you want to set the payload to a scalar object, use - set_payload() instead. - """ - if self._payload is None: - self._payload = [payload] - else: - self._payload.append(payload) - - def get_payload(self, i=None, decode=False): - """Return a reference to the payload. - - The payload will either be a list object or a string. If you mutate - the list object, you modify the message's payload in place. Optional - i returns that index into the payload. - - Optional decode is a flag indicating whether the payload should be - decoded or not, according to the Content-Transfer-Encoding header - (default is False). - - When True and the message is not a multipart, the payload will be - decoded if this header's value is `quoted-printable' or `base64'. If - some other encoding is used, or the header is missing, or if the - payload has bogus data (i.e. bogus base64 or uuencoded data), the - payload is returned as-is. - - If the message is a multipart and the decode flag is True, then None - is returned. - """ - # Here is the logic table for this code, based on the email5.0.0 code: - # i decode is_multipart result - # ------ ------ ------------ ------------------------------ - # None True True None - # i True True None - # None False True _payload (a list) - # i False True _payload element i (a Message) - # i False False error (not a list) - # i True False error (not a list) - # None False False _payload - # None True False _payload decoded (bytes) - # Note that Barry planned to factor out the 'decode' case, but that - # isn't so easy now that we handle the 8 bit data, which needs to be - # converted in both the decode and non-decode path. - if self.is_multipart(): - if decode: - return None - if i is None: - return self._payload - else: - return self._payload[i] - # For backward compatibility, Use isinstance and this error message - # instead of the more logical is_multipart test. - if i is not None and not isinstance(self._payload, list): - raise TypeError("Expected list, got %s" % type(self._payload)) - payload = self._payload - # cte might be a Header, so for now stringify it. - cte = str(self.get("content-transfer-encoding", "")).lower() - # payload may be bytes here. - if isinstance(payload, str): - if utils._has_surrogates(payload): - bpayload = payload.encode("ascii", "surrogateescape") - if not decode: - try: - payload = bpayload.decode(self.get_param("charset", "ascii"), "replace") - except LookupError: - payload = bpayload.decode("ascii", "replace") - elif decode: - try: - bpayload = payload.encode("ascii") - except UnicodeError: - # This won't happen for RFC compliant messages (messages - # containing only ASCII codepoints in the unicode input). - # If it does happen, turn the string into bytes in a way - # guaranteed not to fail. - bpayload = payload.encode("raw-unicode-escape") - if not decode: - return payload - if cte == "quoted-printable": - return utils._qdecode(bpayload) - elif cte == "base64": - # XXX: this is a bit of a hack; decode_b should probably be factored - # out somewhere, but I haven't figured out where yet. - value, defects = decode_b(b"".join(bpayload.splitlines())) - for defect in defects: - self.policy.handle_defect(self, defect) - return value - elif cte in ("x-uuencode", "uuencode", "uue", "x-uue"): - in_file = BytesIO(bpayload) - out_file = BytesIO() - try: - uu.decode(in_file, out_file, quiet=True) - return out_file.getvalue() - except uu.Error: - # Some decoding problem - return bpayload - if isinstance(payload, str): - return bpayload - return payload - - def set_payload(self, payload, charset=None): - """Set the payload to the given value. - - Optional charset sets the message's default character set. See - set_charset() for details. - """ - if isinstance(payload, bytes): - payload = payload.decode("ascii", "surrogateescape") - self._payload = payload - if charset is not None: - self.set_charset(charset) - - def set_charset(self, charset): - """Set the charset of the payload to a given character set. - - charset can be a Charset instance, a string naming a character set, or - None. If it is a string it will be converted to a Charset instance. - If charset is None, the charset parameter will be removed from the - Content-Type field. Anything else will generate a TypeError. - - The message will be assumed to be of type text/* encoded with - charset.input_charset. It will be converted to charset.output_charset - and encoded properly, if needed, when generating the plain text - representation of the message. MIME headers (MIME-Version, - Content-Type, Content-Transfer-Encoding) will be added as needed. - """ - if charset is None: - self.del_param("charset") - self._charset = None - return - if not isinstance(charset, Charset): - charset = Charset(charset) - self._charset = charset - if "MIME-Version" not in self: - self.add_header("MIME-Version", "1.0") - if "Content-Type" not in self: - self.add_header("Content-Type", "text/plain", charset=charset.get_output_charset()) - else: - self.set_param("charset", charset.get_output_charset()) - if charset != charset.get_output_charset(): - self._payload = charset.body_encode(self._payload) - if "Content-Transfer-Encoding" not in self: - cte = charset.get_body_encoding() - try: - cte(self) - except TypeError: - self._payload = charset.body_encode(self._payload) - self.add_header("Content-Transfer-Encoding", cte) - - def get_charset(self): - """Return the Charset instance associated with the message's payload.""" - return self._charset - - # - # MAPPING INTERFACE (partial) - # - def __len__(self): - """Return the total number of headers, including duplicates.""" - return len(self._headers) - - def __getitem__(self, name): - """Get a header value. - - Return None if the header is missing instead of raising an exception. - - Note that if the header appeared multiple times, exactly which - occurrence gets returned is undefined. Use get_all() to get all - the values matching a header field name. - """ - return self.get(name) - - def __setitem__(self, name, val): - """Set the value of a header. - - Note: this does not overwrite an existing header with the same field - name. Use __delitem__() first to delete any existing headers. - """ - max_count = self.policy.header_max_count(name) - if max_count: - lname = name.lower() - found = 0 - for k, v in self._headers: - if k.lower() == lname: - found += 1 - if found >= max_count: - raise ValueError( - "There may be at most {} {} headers " - "in a message".format(max_count, name) - ) - self._headers.append(self.policy.header_store_parse(name, val)) - - def __delitem__(self, name): - """Delete all occurrences of a header, if present. - - Does not raise an exception if the header is missing. - """ - name = name.lower() - newheaders = [] - for k, v in self._headers: - if k.lower() != name: - newheaders.append((k, v)) - self._headers = newheaders - - def __contains__(self, name): - return name.lower() in [k.lower() for k, v in self._headers] - - def __iter__(self): - for field, value in self._headers: - yield field - - def keys(self): - """Return a list of all the message's header field names. - - These will be sorted in the order they appeared in the original - message, or were added to the message, and may contain duplicates. - Any fields deleted and re-inserted are always appended to the header - list. - """ - return [k for k, v in self._headers] - - def values(self): - """Return a list of all the message's header values. - - These will be sorted in the order they appeared in the original - message, or were added to the message, and may contain duplicates. - Any fields deleted and re-inserted are always appended to the header - list. - """ - return [self.policy.header_fetch_parse(k, v) for k, v in self._headers] - - def items(self): - """Get all the message's header fields and values. - - These will be sorted in the order they appeared in the original - message, or were added to the message, and may contain duplicates. - Any fields deleted and re-inserted are always appended to the header - list. - """ - return [(k, self.policy.header_fetch_parse(k, v)) for k, v in self._headers] - - def get(self, name, failobj=None): - """Get a header value. - - Like __getitem__() but return failobj instead of None when the field - is missing. - """ - name = name.lower() - for k, v in self._headers: - if k.lower() == name: - return self.policy.header_fetch_parse(k, v) - return failobj - - # - # "Internal" methods (public API, but only intended for use by a parser - # or generator, not normal application code. - # - - def set_raw(self, name, value): - """Store name and value in the model without modification. - - This is an "internal" API, intended only for use by a parser. - """ - self._headers.append((name, value)) - - def raw_items(self): - """Return the (name, value) header pairs without modification. - - This is an "internal" API, intended only for use by a generator. - """ - return iter(self._headers.copy()) - - # - # Additional useful stuff - # - - def get_all(self, name, failobj=None): - """Return a list of all the values for the named field. - - These will be sorted in the order they appeared in the original - message, and may contain duplicates. Any fields deleted and - re-inserted are always appended to the header list. - - If no such fields exist, failobj is returned (defaults to None). - """ - values = [] - name = name.lower() - for k, v in self._headers: - if k.lower() == name: - values.append(self.policy.header_fetch_parse(k, v)) - if not values: - return failobj - return values - - def add_header(self, _name, _value, **_params): - """Extended header setting. - - name is the header field to add. keyword arguments can be used to set - additional parameters for the header field, with underscores converted - to dashes. Normally the parameter will be added as key="value" unless - value is None, in which case only the key will be added. If a - parameter value contains non-ASCII characters it can be specified as a - three-tuple of (charset, language, value), in which case it will be - encoded according to RFC2231 rules. Otherwise it will be encoded using - the utf-8 charset and a language of ''. - - Examples: - - msg.add_header('content-disposition', 'attachment', filename='bud.gif') - msg.add_header('content-disposition', 'attachment', - filename=('utf-8', '', Fußballer.ppt')) - msg.add_header('content-disposition', 'attachment', - filename='Fußballer.ppt')) - """ - parts = [] - for k, v in _params.items(): - if v is None: - parts.append(k.replace("_", "-")) - else: - parts.append(_formatparam(k.replace("_", "-"), v)) - if _value is not None: - parts.insert(0, _value) - self[_name] = SEMISPACE.join(parts) - - def replace_header(self, _name, _value): - """Replace a header. - - Replace the first matching header found in the message, retaining - header order and case. If no matching header was found, a KeyError is - raised. - """ - _name = _name.lower() - for i, (k, v) in zip(range(len(self._headers)), self._headers): - if k.lower() == _name: - self._headers[i] = self.policy.header_store_parse(k, _value) - break - else: - raise KeyError(_name) - - # - # Use these three methods instead of the three above. - # - - def get_content_type(self): - """Return the message's content type. - - The returned string is coerced to lower case of the form - `maintype/subtype'. If there was no Content-Type header in the - message, the default type as given by get_default_type() will be - returned. Since according to RFC 2045, messages always have a default - type this will always return a value. - - RFC 2045 defines a message's default type to be text/plain unless it - appears inside a multipart/digest container, in which case it would be - message/rfc822. - """ - missing = object() - value = self.get("content-type", missing) - if value is missing: - # This should have no parameters - return self.get_default_type() - ctype = _splitparam(value)[0].lower() - # RFC 2045, section 5.2 says if its invalid, use text/plain - if ctype.count("/") != 1: - return "text/plain" - return ctype - - def get_content_maintype(self): - """Return the message's main content type. - - This is the `maintype' part of the string returned by - get_content_type(). - """ - ctype = self.get_content_type() - return ctype.split("/")[0] - - def get_content_subtype(self): - """Returns the message's sub-content type. - - This is the `subtype' part of the string returned by - get_content_type(). - """ - ctype = self.get_content_type() - return ctype.split("/")[1] - - def get_default_type(self): - """Return the `default' content type. - - Most messages have a default content type of text/plain, except for - messages that are subparts of multipart/digest containers. Such - subparts have a default content type of message/rfc822. - """ - return self._default_type - - def set_default_type(self, ctype): - """Set the `default' content type. - - ctype should be either "text/plain" or "message/rfc822", although this - is not enforced. The default content type is not stored in the - Content-Type header. - """ - self._default_type = ctype - - def _get_params_preserve(self, failobj, header): - # Like get_params() but preserves the quoting of values. BAW: - # should this be part of the public interface? - missing = object() - value = self.get(header, missing) - if value is missing: - return failobj - params = [] - for p in _parseparam(value): - try: - name, val = p.split("=", 1) - name = name.strip() - val = val.strip() - except ValueError: - # Must have been a bare attribute - name = p.strip() - val = "" - params.append((name, val)) - params = utils.decode_params(params) - return params - - def get_params(self, failobj=None, header="content-type", unquote=True): - """Return the message's Content-Type parameters, as a list. - - The elements of the returned list are 2-tuples of key/value pairs, as - split on the `=' sign. The left hand side of the `=' is the key, - while the right hand side is the value. If there is no `=' sign in - the parameter the value is the empty string. The value is as - described in the get_param() method. - - Optional failobj is the object to return if there is no Content-Type - header. Optional header is the header to search instead of - Content-Type. If unquote is True, the value is unquoted. - """ - missing = object() - params = self._get_params_preserve(missing, header) - if params is missing: - return failobj - if unquote: - return [(k, _unquotevalue(v)) for k, v in params] - else: - return params - - def get_param(self, param, failobj=None, header="content-type", unquote=True): - """Return the parameter value if found in the Content-Type header. - - Optional failobj is the object to return if there is no Content-Type - header, or the Content-Type header has no such parameter. Optional - header is the header to search instead of Content-Type. - - Parameter keys are always compared case insensitively. The return - value can either be a string, or a 3-tuple if the parameter was RFC - 2231 encoded. When it's a 3-tuple, the elements of the value are of - the form (CHARSET, LANGUAGE, VALUE). Note that both CHARSET and - LANGUAGE can be None, in which case you should consider VALUE to be - encoded in the us-ascii charset. You can usually ignore LANGUAGE. - The parameter value (either the returned string, or the VALUE item in - the 3-tuple) is always unquoted, unless unquote is set to False. - - If your application doesn't care whether the parameter was RFC 2231 - encoded, it can turn the return value into a string as follows: - - param = msg.get_param('foo') - param = email.utils.collapse_rfc2231_value(rawparam) - - """ - if header not in self: - return failobj - for k, v in self._get_params_preserve(failobj, header): - if k.lower() == param.lower(): - if unquote: - return _unquotevalue(v) - else: - return v - return failobj - - def set_param( - self, param, value, header="Content-Type", requote=True, charset=None, language="" - ): - """Set a parameter in the Content-Type header. - - If the parameter already exists in the header, its value will be - replaced with the new value. - - If header is Content-Type and has not yet been defined for this - message, it will be set to "text/plain" and the new parameter and - value will be appended as per RFC 2045. - - An alternate header can specified in the header argument, and all - parameters will be quoted as necessary unless requote is False. - - If charset is specified, the parameter will be encoded according to RFC - 2231. Optional language specifies the RFC 2231 language, defaulting - to the empty string. Both charset and language should be strings. - """ - if not isinstance(value, tuple) and charset: - value = (charset, language, value) - - if header not in self and header.lower() == "content-type": - ctype = "text/plain" - else: - ctype = self.get(header) - if not self.get_param(param, header=header): - if not ctype: - ctype = _formatparam(param, value, requote) - else: - ctype = SEMISPACE.join([ctype, _formatparam(param, value, requote)]) - else: - ctype = "" - for old_param, old_value in self.get_params(header=header, unquote=requote): - append_param = "" - if old_param.lower() == param.lower(): - append_param = _formatparam(param, value, requote) - else: - append_param = _formatparam(old_param, old_value, requote) - if not ctype: - ctype = append_param - else: - ctype = SEMISPACE.join([ctype, append_param]) - if ctype != self.get(header): - del self[header] - self[header] = ctype - - def del_param(self, param, header="content-type", requote=True): - """Remove the given parameter completely from the Content-Type header. - - The header will be re-written in place without the parameter or its - value. All values will be quoted as necessary unless requote is - False. Optional header specifies an alternative to the Content-Type - header. - """ - if header not in self: - return - new_ctype = "" - for p, v in self.get_params(header=header, unquote=requote): - if p.lower() != param.lower(): - if not new_ctype: - new_ctype = _formatparam(p, v, requote) - else: - new_ctype = SEMISPACE.join([new_ctype, _formatparam(p, v, requote)]) - if new_ctype != self.get(header): - del self[header] - self[header] = new_ctype - - def set_type(self, type, header="Content-Type", requote=True): - """Set the main type and subtype for the Content-Type header. - - type must be a string in the form "maintype/subtype", otherwise a - ValueError is raised. - - This method replaces the Content-Type header, keeping all the - parameters in place. If requote is False, this leaves the existing - header's quoting as is. Otherwise, the parameters will be quoted (the - default). - - An alternative header can be specified in the header argument. When - the Content-Type header is set, we'll always also add a MIME-Version - header. - """ - # BAW: should we be strict? - if not type.count("/") == 1: - raise ValueError - # Set the Content-Type, you get a MIME-Version - if header.lower() == "content-type": - del self["mime-version"] - self["MIME-Version"] = "1.0" - if header not in self: - self[header] = type - return - params = self.get_params(header=header, unquote=requote) - del self[header] - self[header] = type - # Skip the first param; it's the old type. - for p, v in params[1:]: - self.set_param(p, v, header, requote) - - def get_filename(self, failobj=None): - """Return the filename associated with the payload if present. - - The filename is extracted from the Content-Disposition header's - `filename' parameter, and it is unquoted. If that header is missing - the `filename' parameter, this method falls back to looking for the - `name' parameter. - """ - missing = object() - filename = self.get_param("filename", missing, "content-disposition") - if filename is missing: - filename = self.get_param("name", missing, "content-type") - if filename is missing: - return failobj - return utils.collapse_rfc2231_value(filename).strip() - - def get_boundary(self, failobj=None): - """Return the boundary associated with the payload if present. - - The boundary is extracted from the Content-Type header's `boundary' - parameter, and it is unquoted. - """ - missing = object() - boundary = self.get_param("boundary", missing) - if boundary is missing: - return failobj - # RFC 2046 says that boundaries may begin but not end in w/s - return utils.collapse_rfc2231_value(boundary).rstrip() - - def set_boundary(self, boundary): - """Set the boundary parameter in Content-Type to 'boundary'. - - This is subtly different than deleting the Content-Type header and - adding a new one with a new boundary parameter via add_header(). The - main difference is that using the set_boundary() method preserves the - order of the Content-Type header in the original message. - - HeaderParseError is raised if the message has no Content-Type header. - """ - missing = object() - params = self._get_params_preserve(missing, "content-type") - if params is missing: - # There was no Content-Type header, and we don't know what type - # to set it to, so raise an exception. - raise errors.HeaderParseError("No Content-Type header found") - newparams = [] - foundp = False - for pk, pv in params: - if pk.lower() == "boundary": - newparams.append(("boundary", '"%s"' % boundary)) - foundp = True - else: - newparams.append((pk, pv)) - if not foundp: - # The original Content-Type header had no boundary attribute. - # Tack one on the end. BAW: should we raise an exception - # instead??? - newparams.append(("boundary", '"%s"' % boundary)) - # Replace the existing Content-Type header with the new value - newheaders = [] - for h, v in self._headers: - if h.lower() == "content-type": - parts = [] - for k, v in newparams: - if v == "": - parts.append(k) - else: - parts.append("%s=%s" % (k, v)) - val = SEMISPACE.join(parts) - newheaders.append(self.policy.header_store_parse(h, val)) - - else: - newheaders.append((h, v)) - self._headers = newheaders - - def get_content_charset(self, failobj=None): - """Return the charset parameter of the Content-Type header. - - The returned string is always coerced to lower case. If there is no - Content-Type header, or if that header has no charset parameter, - failobj is returned. - """ - missing = object() - charset = self.get_param("charset", missing) - if charset is missing: - return failobj - if isinstance(charset, tuple): - # RFC 2231 encoded, so decode it, and it better end up as ascii. - pcharset = charset[0] or "us-ascii" - try: - # LookupError will be raised if the charset isn't known to - # Python. UnicodeError will be raised if the encoded text - # contains a character not in the charset. - as_bytes = charset[2].encode("raw-unicode-escape") - charset = str(as_bytes, pcharset) - except (LookupError, UnicodeError): - charset = charset[2] - # charset characters must be in us-ascii range - try: - charset.encode("us-ascii") - except UnicodeError: - return failobj - # RFC 2046, $4.1.2 says charsets are not case sensitive - return charset.lower() - - def get_charsets(self, failobj=None): - """Return a list containing the charset(s) used in this message. - - The returned list of items describes the Content-Type headers' - charset parameter for this message and all the subparts in its - payload. - - Each item will either be a string (the value of the charset parameter - in the Content-Type header of that part) or the value of the - 'failobj' parameter (defaults to None), if the part does not have a - main MIME type of "text", or the charset is not defined. - - The list will contain one string for each part of the message, plus - one for the container message (i.e. self), so that a non-multipart - message will still return a list of length 1. - """ - return [part.get_content_charset(failobj) for part in self.walk()] - - # I.e. def walk(self): ... - from email.iterators import walk diff --git a/modules/ulinksdk/email/parser.py b/modules/ulinksdk/email/parser.py deleted file mode 100644 index 760adeff50..0000000000 --- a/modules/ulinksdk/email/parser.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (C) 2001-2007 Python Software Foundation -# Author: Barry Warsaw, Thomas Wouters, Anthony Baxter -# Contact: email-sig@python.org - -"""A parser of RFC 2822 and MIME email messages.""" - -__all__ = ["Parser", "HeaderParser", "BytesParser", "BytesHeaderParser"] - -import warnings -from io import StringIO, TextIOWrapper - -from email.feedparser import FeedParser, BytesFeedParser -from email.message import Message -from email._policybase import compat32 - - -class Parser: - def __init__(self, _class=Message, policy=compat32): - """Parser of RFC 2822 and MIME email messages. - - Creates an in-memory object tree representing the email message, which - can then be manipulated and turned over to a Generator to return the - textual representation of the message. - - The string must be formatted as a block of RFC 2822 headers and header - continuation lines, optionally preceeded by a `Unix-from' header. The - header block is terminated either by the end of the string or by a - blank line. - - _class is the class to instantiate for new message objects when they - must be created. This class must have a constructor that can take - zero arguments. Default is Message.Message. - - The policy keyword specifies a policy object that controls a number of - aspects of the parser's operation. The default policy maintains - backward compatibility. - - """ - self._class = _class - self.policy = policy - - def parse(self, fp, headersonly=False): - """Create a message structure from the data in a file. - - Reads all the data from the file and returns the root of the message - structure. Optional headersonly is a flag specifying whether to stop - parsing after reading the headers or not. The default is False, - meaning it parses the entire contents of the file. - """ - feedparser = FeedParser(self._class, policy=self.policy) - if headersonly: - feedparser._set_headersonly() - while True: - data = fp.read(8192) - if not data: - break - feedparser.feed(data) - return feedparser.close() - - def parsestr(self, text, headersonly=False): - """Create a message structure from a string. - - Returns the root of the message structure. Optional headersonly is a - flag specifying whether to stop parsing after reading the headers or - not. The default is False, meaning it parses the entire contents of - the file. - """ - return self.parse(StringIO(text), headersonly=headersonly) - - -class HeaderParser(Parser): - def parse(self, fp, headersonly=True): - return Parser.parse(self, fp, True) - - def parsestr(self, text, headersonly=True): - return Parser.parsestr(self, text, True) - - -class BytesParser: - def __init__(self, *args, **kw): - """Parser of binary RFC 2822 and MIME email messages. - - Creates an in-memory object tree representing the email message, which - can then be manipulated and turned over to a Generator to return the - textual representation of the message. - - The input must be formatted as a block of RFC 2822 headers and header - continuation lines, optionally preceeded by a `Unix-from' header. The - header block is terminated either by the end of the input or by a - blank line. - - _class is the class to instantiate for new message objects when they - must be created. This class must have a constructor that can take - zero arguments. Default is Message.Message. - """ - self.parser = Parser(*args, **kw) - - def parse(self, fp, headersonly=False): - """Create a message structure from the data in a binary file. - - Reads all the data from the file and returns the root of the message - structure. Optional headersonly is a flag specifying whether to stop - parsing after reading the headers or not. The default is False, - meaning it parses the entire contents of the file. - """ - fp = TextIOWrapper(fp, encoding="ascii", errors="surrogateescape") - with fp: - return self.parser.parse(fp, headersonly) - - def parsebytes(self, text, headersonly=False): - """Create a message structure from a byte string. - - Returns the root of the message structure. Optional headersonly is a - flag specifying whether to stop parsing after reading the headers or - not. The default is False, meaning it parses the entire contents of - the file. - """ - text = text.decode("ASCII", errors="surrogateescape") - return self.parser.parsestr(text, headersonly) - - -class BytesHeaderParser(BytesParser): - def parse(self, fp, headersonly=True): - return BytesParser.parse(self, fp, headersonly=True) - - def parsebytes(self, text, headersonly=True): - return BytesParser.parsebytes(self, text, headersonly=True) diff --git a/modules/ulinksdk/upload_file.py b/modules/ulinksdk/fileupload.py similarity index 68% rename from modules/ulinksdk/upload_file.py rename to modules/ulinksdk/fileupload.py index 2faedbf8d7..d9ed629019 100644 --- a/modules/ulinksdk/upload_file.py +++ b/modules/ulinksdk/fileupload.py @@ -3,8 +3,49 @@ import _thread import urandom as random import struct -from crc16 import crc_ibm +crc_ibm_table = [ + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, +] +def crc_ibm (data): + global crc_ibm_table + crc = 0x0000 + lut = 0 + for byte in data: + lut = (crc ^ (byte)) & 0xFF + crc = (crc >> 8) ^ crc_ibm_table[lut] + return crc class UPload: @@ -27,21 +68,21 @@ class UPload: UPLOAD_FILE_HAVE_FINISHED = 78124 STATE_MQTT_UPLOAD_MQTT_HANDLE_IS_NULL = -0x2101 MQTT_UPLOAD_DEFAULT_SEND_MAX_SIZE = 128 * 1024 - MQTT_UPLOAD_DEFAULT_SEND_MIN_SIZE = 256 - + MQTT_UPLOAD_DEFAULT_SEND_MIN_SIZE = 256 + UPLOAD_FILE_OK = 200 + UPLOAD_FILE_PARAMETER_ERROR =460 UPLOAD_FILE_FILE_BLOCK_DUPLICATION = 78126 - - default_block_size = (2 * 1024) + + default_block_size = ( 2 * 1024) retry_max_value = 5 rsp_timeout = 2000 def __init__(self, lk_handle = None): - if lk_handle == None: raise ValueError("lk_handle wrong") - + self.conflictStrategy = "overwrite" self.up_task_list = list() @@ -49,38 +90,34 @@ def __init__(self, lk_handle = None): self.uptask_lock = _thread.allocate_lock() self.open_lock = _thread.allocate_lock() + self.file_data_desc_lock = _thread.allocate_lock() self.result = dict() - self.g_data = None - self.g_data_len = 0 - self.g_file_path = None - + self.file_data_desc_list = list() def upload_subsribe(self): - #print("start to subsribe") #test point + # print("start to subsribe") #test point topic01 = self.lk_handle.TOPIC["upload_init_reply"].format(self.lk_handle.productKey, self.lk_handle.deviceName) topic02 = self.lk_handle.TOPIC["upload_file_reply"].format(self.lk_handle.productKey, self.lk_handle.deviceName) topic03 = self.lk_handle.TOPIC["upload_cancel_reply"].format(self.lk_handle.productKey, self.lk_handle.deviceName) - #print("subsribe:", topic01) #test point + # print("subsribe:", topic01) #test point self.lk_handle.lk_subscribe(topic01) - #print("subsribe:", topic02) #test point + # print("subsribe:", topic02) #test point self.lk_handle.lk_subscribe(topic02) - #print("subsribe:", topic03) #test point + # print("subsribe:", topic03) #test point self.lk_handle.lk_subscribe(topic03) - sub_node01 = self.lk_handle.__create_subscribe_node(topic01, + sub_node01 = self.lk_handle.__create_subscribe_node(topic01, self._upload_receive_init_response_handler) - + self.lk_handle.__subsribe_list_insert(sub_node01) - - sub_node02 = self.lk_handle.__create_subscribe_node(topic02, + sub_node02 = self.lk_handle.__create_subscribe_node(topic02, self._upload_receive_upload_response_handler) self.lk_handle.__subsribe_list_insert(sub_node02) - - sub_node03 = self.lk_handle.__create_subscribe_node(topic03, + sub_node03 = self.lk_handle.__create_subscribe_node(topic03, self._upload_receive_cancel_response_handler) self.lk_handle.__subsribe_list_insert(sub_node03) @@ -100,7 +137,7 @@ def upload_file_task_init(self, filename, filesize, mode, digest, uuid, read_dat up_task['mode'] = mode up_task['crc64'] = digest # no crc up_task['uuid'] = uuid - up_task['upload_id'] = None + up_task['upload_id'] = None up_task['rsp_code'] = None up_task['packet'] = dict() up_task['packet']['type'] = None @@ -109,12 +146,45 @@ def upload_file_task_init(self, filename, filesize, mode, digest, uuid, read_dat up_task['packet']['desc']['upload_id'] = None up_task['data'] = None up_task['block_size'] = 0 - return up_task - + def upload_file_task_insert(self, up_task): self.up_task_list.append(up_task) + def file_data_desc_insert(self, filename, data, data_len): + #print("file name:", filename) # test point + self.file_data_desc_lock.acquire() + found = 0 + for index in range(len(self.file_data_desc_list)): + if (self.file_data_desc_list[index]['file_name'] == filename): + found = 1 + break + file_data_desc = dict() + if data_len == 0: + file_data_desc['file_name'] = filename + file_data_desc['data'] = None + file_data_desc['data_len'] = 0 + file_data_desc['file_path'] = data + #print("file path:", data) # test point + file_data_desc['file_len'] = self.get_file_size(data) + #print("file path file size:", file_data_desc['file_len']) # test point + + else: + file_data_desc['file_name'] = filename + file_data_desc['data'] = data + file_data_desc['data_len'] = data_len + file_data_desc['file_path'] = None + file_data_desc['file_len'] = data_len + + if (found == 1): # file already exits in the list + self.file_data_desc_list[index] = file_data_desc + else: # file not exits in the list + self.file_data_desc_list.append(file_data_desc) + + self.file_data_desc_lock.release() + + return file_data_desc['file_len'] + def upload_block_size_is_valid(self, block_size, is_final): if block_size == 0: return 0 @@ -131,27 +201,36 @@ def upload_block_size_is_valid(self, block_size, is_final): def upload_read_data_handler(self, packet=None, up_task=None, size=0, userdata=None): read_len = 0 - if (packet['desc']['code'] == self.UPLOAD_FILE_OK): - if (up_task!=None and size != 0) : - read_size = size - file_name = packet['desc']['file_name'] - if (self.g_data != None): - offset = packet['desc']['file_offset'] - if (read_size > self.g_data_len - offset): - read_len = self.g_data_len - offset - else: - read_len = read_size - - up_task['data'] = self.g_data[offset: offset+read_len] # probably is not ok - else: - with open(self.g_file_path, 'rb') as f: - offset = packet['desc']['file_offset'] - f.seek(offset,0) - # print("read_size:", read_size) - up_task['data'] = f.read(read_size) - read_len = len(up_task['data']) - # print("Open {} read at: {},Read_len: {}".format(file_name, offset, read_len)) # test point - + self.file_data_desc_lock.acquire() + found = 0 + if up_task != None: + for index in range(len(self.file_data_desc_list)): + file_data_desc = self.file_data_desc_list[index] + if file_data_desc['file_name'] == up_task['file_name']: + found = 1 + if (packet['desc']['code'] == self.UPLOAD_FILE_OK): + if (up_task!=None and size != 0) : + read_size = size + file_name = packet['desc']['file_name'] + if (file_data_desc['data'] != None): + offset = packet['desc']['file_offset'] + if (read_size > file_data_desc['data_len'] - offset): + read_len = file_data_desc['data_len'] - offset + else: + read_len = read_size + up_task['data'] = file_data_desc['data'][offset: offset+read_len] # probably is not ok + else: + with open(file_data_desc['file_path'], 'rb') as f: + offset = packet['desc']['file_offset'] + f.seek(offset,0) + # print("read_size:", read_size) + up_task['data'] = f.read(read_size) + read_len = len(up_task['data']) + # print("Open {} read at: {},Read_len: {}".format(file_name, offset, read_len)) # test point + break + self.file_data_desc_lock.release() + #if found == 0: + # raise ValueError("file not found from read data handler!") return read_len def upload_recv_response_process(self, type, msg, up_task): @@ -160,20 +239,18 @@ def upload_recv_response_process(self, type, msg, up_task): if msg['desc']['code'] == self.UPLOAD_FILE_OK: up_task['status'] = self.STATE_MQTT_UPLOAD_CANCEL_SUCCESS up_task['failed_count'] = 0 - elif msg['desc']['code'] == self.UPLOAD_FILE_FILE_BLOCK_DUPLICATION: + # print("UPLOAD_FILE_FILE_BLOCK_DUPLICATION resend") res = self.upload_resend_pub(up_task) if res != 0: up_task['status'] = self.STATE_MQTT_UPLOAD_CANCEL_FAILED - else: up_task['status'] = self.STATE_MQTT_UPLOAD_CANCEL_FAILED if ( (up_task['read_data_handler'] != None) and \ (up_task['status'] == self.STATE_MQTT_UPLOAD_CANCEL_FAILED or up_task['status'] == self.STATE_MQTT_UPLOAD_CANCEL_SUCCESS ) ): - up_task['read_data_handler'](msg, None, 0, up_task['userdata']) - + else: return @@ -190,7 +267,7 @@ def upload_recv_response_process(self, type, msg, up_task): msg['desc']['file_offset'] = msg['desc']['file_offset'] + msg['desc']['block_size'] # print("status:", up_task['status']) #test point # print("UPLOAD_FILE_OK") #test point - + data_len = 0 if (up_task['read_data_handler'] != None): data_len = up_task['read_data_handler'](msg, up_task, self.default_block_size, up_task['userdata']) @@ -199,32 +276,42 @@ def upload_recv_response_process(self, type, msg, up_task): # print("Read data_len:{}".format(data_len)) #test point # print("data to upload:", up_task['data']) #test point - + is_final = 0 if (up_task['file_offset'] + data_len) >= up_task['file_size']: is_final = 1 # print('data_len:', data_len, 'up_task file_offset:', up_task['file_offset'], \ # 'up_task file_size:', up_task['file_size'], "is final:", is_final) #test point - if ( (data_len != 0) and (up_task['data'] != None) and (self.upload_block_size_is_valid(data_len, is_final) == 1) ): + block_size_is_valid = self.upload_block_size_is_valid(data_len, is_final) + if ( (data_len != 0) and (up_task['data'] != None) and (block_size_is_valid == 1) ): + #print('upload_recv_response_process send_block_file:','block_size:', up_task['block_size'],\ + # 'packet:', up_task['packet']) + # print('\nupload_recv_response_process send_block_file:') # test point res = self.upload_send_block_file(up_task, up_task['data'], data_len, up_task['file_offset'], 0) if res == 0: up_task['block_size'] = data_len else: + print("STATE_MQTT_UPLOAD_FAILED! upload_send_block_file res: ", res) up_task['status'] = self.STATE_MQTT_UPLOAD_FAILED else: + print("STATE_MQTT_UPLOAD_FAILED! data_len %s, up_task['data']: %s, block_size_is_valid: %s" %\ + (data_len, up_task['data'], block_size_is_valid)) up_task['status'] = self.STATE_MQTT_UPLOAD_FAILED return elif msg['desc']['code'] == self.UPLOAD_FILE_FAILED_BLOCK_CRC: + # print("UPLOAD_FILE_FAILED_BLOCK_CRC resend") # test point self.upload_resend_pub(up_task) - + else: - if msg['desc']['code'] == self.UPLOAD_FILE_FAILED_WHOLE_CHECK: + if msg['desc']['code'] == self.UPLOAD_FILE_FAILED_WHOLE_CHECK: up_task['status'] = self.STATE_MQTT_UPLOAD_FAILED_WHOLE_CHECK elif msg['desc']['code'] == self.UPLOAD_FILE_HAVE_FINISHED: up_task['status'] = self.STATE_MQTT_UPLOAD_FINISHED else: + # print("upload fail!!, code:", msg['desc']['code']) # test point + print("STATE_MQTT_UPLOAD_FAILED! msg desc code:", msg['desc']['code']) up_task['status'] = self.STATE_MQTT_UPLOAD_FAILED if up_task['read_data_handler'] != None: @@ -232,13 +319,12 @@ def upload_recv_response_process(self, type, msg, up_task): else: pass - + def _upload_receive_init_response_handler(self, msg_data): # print("file up init reply received:", msg_data) #test point res = 0 msg = dict() msg['type'] = self.AIOT_MQTT_UPLOADRECV_INIT_REPLY - msg['desc'] = dict() msg['desc']['file_name'] = msg_data['data']['fileName'] msg['desc']['upload_id'] = msg_data['data']['uploadId'] @@ -247,18 +333,15 @@ def _upload_receive_init_response_handler(self, msg_data): msg['desc']['file_size'] = msg_data['data']['fileSize'] msg['desc']['file_offset'] = msg_data['data']['offset'] msg['desc']['complete'] = 0 - - self.uptask_lock.acquire() for index in range(len(self.up_task_list)): if (msg['desc']['file_name'] != None) and (msg['desc']['file_name'] == self.up_task_list[index]['file_name']): - if msg['desc']['upload_id'] == None: print("[ERROR] Receive uploadID is NULL") else: self.up_task_list[index]['upload_id'] = msg['desc']['upload_id'] msg['desc']['block_size'] = 0 - + self.up_task_list[index]['is_rsp'] = 1 self.up_task_list[index]['rsp_code'] = msg['desc']['code'] @@ -271,12 +354,12 @@ def _upload_receive_init_response_handler(self, msg_data): # print("file up init reply msg matched!") #test point self.upload_recv_response_process(self.AIOT_MQTT_UPLOADRECV_INIT_REPLY, msg, self.up_task_list[index]) - break - + self.uptask_lock.release() + return self.uptask_lock.release() + return # print("Recv file name{} ".format(msg['desc']['file_name'])) #test point - def _upload_receive_upload_response_handler(self, msg_data): # print("file up upload reply received:", msg_data) #test point res = 0 @@ -289,9 +372,7 @@ def _upload_receive_upload_response_handler(self, msg_data): msg['desc']['code'] = msg_data['code'] #msg['desc']['file_size'] = msg_data['data']['fileSize'] msg['desc']['file_offset'] = msg_data['data']['offset'] - msg['desc']['block_size'] = msg_data['data']['bSize'] - try: complete = msg_data['data']['complete'] if complete == True: @@ -312,7 +393,8 @@ def _upload_receive_upload_response_handler(self, msg_data): # print("status:", self.up_task_list[index]['status']) #test point break if (msg['desc']['file_offset'] != self.up_task_list[index]['file_offset']) or (msg['desc']['block_size'] != self.up_task_list[index]['block_size']): - print("Receive upload ACK error,file_offset:{}, block_szie:{}".format(msg['desc']['file_offset'], msg['desc']['block_size'])) + #print("Receive upload ACK error,file_offset:{}, block_szie:{}".format(msg['desc']['file_offset'], msg['desc']['block_size'])) + pass else: msg['desc']['file_name'] = self.up_task_list[index]['file_name'] self.up_task_list[index]['is_rsp'] = 1 @@ -320,14 +402,13 @@ def _upload_receive_upload_response_handler(self, msg_data): self.upload_recv_response_process(self.AIOT_MQTT_UPLOADRECV_UPLOAD_REPLY, msg, self.up_task_list[index]) break self.uptask_lock.release() - + def _upload_receive_cancel_response_handler(self, msg_data): # print("file up cancel reply received:", msg_data) #test point res = 0 msg = dict() msg['type'] = self.AIOT_MQTT_UPLOADRECV_CANCEL_REPLY - msg['desc'] = dict() msg['desc']['upload_id'] = msg_data['data']['uploadId'] msg['desc']['message'] = msg_data['message'] @@ -348,20 +429,25 @@ def _upload_receive_cancel_response_handler(self, msg_data): def upload_send_request_init(self, filename, filesize, mode, digest, uuid): init_topic = self.lk_handle.TOPIC['upload_init'].format(self.lk_handle.productKey, self.lk_handle.deviceName) + param_dic = dict() payload_dic = dict() - param_dic['fileName'] = str(filename) - param_dic['fileSize'] = str(filesize) + param_dic['fileSize'] = filesize param_dic['conflictStrategy'] = str(mode) # self.conflictStrategy param_dic['initUid'] = uuid - param = json.dumps(param_dic) - payload_dic['id'] = str(self.lk_handle.global_alink_id_next()) - payload_dic['params'] = param + id = self.lk_handle.global_alink_id_next() + payload_dic['id'] = str(id) + payload_dic['params'] = param_dic payload = json.dumps(payload_dic) - + payload = payload.replace('\\','') + + payload = '{\"id\":\"%s\",\"params\":{\"fileName\":\"%s\",\"fileSize\":%s,\"conflictStrategy\":\"%s\",\"initUid\":\"%s\"}}' % \ + (id, filename, filesize, mode, uuid) + payload = payload.replace(' ', '') + # print("publish", json.dumps(payload)) #test point return self.lk_handle.lk_publish(init_topic, payload) @@ -381,11 +467,8 @@ def upload_open_stream(self, filename, packet): self.uptask_lock.acquire() index = 0 for index in range(len(self.up_task_list)): - if (self.up_task_list[index]['file_name'] == filename): - self.up_task_list[index]['is_rsp'] = 0 - res = self.upload_send_request_init(self.up_task_list[index]['file_name'], self.up_task_list[index]['file_size'], \ self.up_task_list[index]['mode'], self.up_task_list[index]['crc64'], self.up_task_list[index]['uuid']) self.up_task_list[index]['send_last_time'] = int(time.time() * 1000) @@ -409,17 +492,19 @@ def upload_open_stream(self, filename, packet): # print('waiting cloud response...') #test point time.sleep(0.02) i = i+1 - if i == 100: + if i == 1000: print('Wait init response timeout.') break if self.up_task_list[index]['is_rsp'] == 0: + print("STATE_MQTT_UPLOAD_FAILED! request init fail") res = self.STATE_MQTT_UPLOAD_FAILED self.up_task_list[index]['is_destory'] =1 self.open_lock.release() return res - if (self.up_task_list[index]['rsp_code'] != self.UPLOAD_FILE_OK): + if (self.up_task_list[index]['rsp_code'] != self.UPLOAD_FILE_OK ): + print("STATE_MQTT_UPLOAD_FAILED! request init fail") res = self.STATE_MQTT_UPLOAD_FAILED self.up_task_list[index]['is_destory'] =1 self.open_lock.release() @@ -434,36 +519,24 @@ def upload_send_block_file(self, up_task, data, block_size, file_offset, iscompl res = 0 if (up_task == None): return self.STATE_MQTT_UPLOAD_UPTASK_IS_NULL - + if (data == None): return self.STATE_MQTT_UPLOAD_MQTT_HANDLE_IS_NULL - - params_dic = dict() - - params_dic['uploadId'] = str(up_task['upload_id']) - params_dic['offset'] = str(file_offset) - params_dic['bSize'] = str(block_size) if iscomplete == 1: - params_dic['isComplete'] = "true" + str_isComplete ="true" else: - params_dic['isComplete'] = "false" - - params = json.dumps(params_dic) + str_isComplete ="false" id = self.lk_handle.global_alink_id_next() - head_payload_dic = dict() - head_payload_dic['id'] = str(id) - head_payload_dic['params'] = params - - head_payload = json.dumps(head_payload_dic) - + head_payload = '{\"id\":"%s\",\"params\":{\"uploadId\":\"%s\",\"offset\":%s,\"bSize\":%s,\"isComplete\":%s}}' % \ + (id, up_task['upload_id'], file_offset, block_size, str_isComplete ) + head_payload = head_payload.replace(' ', '') + #print("head payload:", head_payload) topic = self.lk_handle.TOPIC['upload_file'].format(self.lk_handle.productKey, self.lk_handle.deviceName) - head_len = len(head_payload.encode('utf-8')) payload_len = up_task['block_size'] + head_len + 4 - if type(data) == type('str') : data = data.encode('utf-8') @@ -473,31 +546,26 @@ def upload_send_block_file(self, up_task, data, block_size, file_offset, iscompl # print("head_len", head_len) #test point head_len_net = ((head_len>>8) & 0x00FF) + ((head_len << 8) & 0xff00) - - payload = struct.pack('H{}s{}sH'.format(head_len,len(data)), head_len_net, head_payload.encode('utf-8'), data, crc16) + payload = struct.pack(' self.retry_max_value): up_task['status'] = self.STATE_MQTT_UPLOAD_FAILED_TIMEOUT up_task['failed_count'] = 0 @@ -518,70 +584,71 @@ def upload_resend_pub(self, up_task): if ((up_task['status'] == self.STATE_MQTT_UPLOAD_IS_UPLOADING) and (up_task["block_size"] != 0)): # print("blocksize input:", up_task["block_size"]) #test point res = self.upload_send_block_file(up_task, up_task['data'], up_task['block_size'], up_task['file_offset'], 0) - elif up_task['status'] == self.STATE_MQTT_UPLOAD_REQUEST_CANCEL : + elif up_task['status'] == self.STATE_MQTT_UPLOAD_REQUEST_CANCEL : res = self.upload_send_request_cancel(up_task) return res - def upload_process(self): - + def upload_process(self, filename): self.result['status'] = self.STATE_MQTT_UPLOAD_NONE self.result['err_code'] = 0 - self.uptask_lock.acquire() for index in range(len(self.up_task_list)): - self.result['status'] = self.up_task_list[index]['status'] self.result['file_name'] = self.up_task_list[index]['file_name'] self.result['uploadid'] = self.up_task_list[index]['upload_id'] - if self.result['status'] == self.STATE_MQTT_UPLOAD_NONE: pass - elif self.result['status'] == self.STATE_MQTT_UPLOAD_REQUEST_INIT: now = int(time.time() * 1000) - if (now - self.up_task_list[index]['send_last_time']) > 2000: + if (now - self.up_task_list[index]['send_last_time']) > 20000: + print("STATE_MQTT_UPLOAD_REQUEST_INIT resend") + self.up_task_list[index]['send_last_time'] = now self.upload_resend_pub(self.up_task_list[index]) - + elif self.result['status'] == self.STATE_MQTT_UPLOAD_IS_UPLOADING: now = int(time.time() * 1000) - if (now - self.up_task_list[index]['send_last_time']) > 2000: + if (now - self.up_task_list[index]['send_last_time']) > 20000: + print("STATE_MQTT_UPLOAD_IS_UPLOADING resend") + self.up_task_list[index]['send_last_time'] = now self.upload_resend_pub(self.up_task_list[index]) elif self.result['status'] == self.STATE_MQTT_UPLOAD_REQUEST_CANCEL: now = int(time.time() * 1000) if (now - self.up_task_list[index]['send_last_time']) > 2000: + # print("STATE_MQTT_UPLOAD_REQUEST_CANCEL resend") + self.up_task_list[index]['send_last_time'] = now self.upload_resend_pub(self.up_task_list[index]) - + elif (self.result['status'] == self.STATE_MQTT_UPLOAD_FINISHED) or \ self.result['status'] == self.STATE_MQTT_UPLOAD_CANCEL_SUCCESS: - + self.up_task_list[index]['status'] = self.STATE_MQTT_UPLOAD_NONE self.result['err_code'] = self.up_task_list[index]['rsp_code'] + # print("Finish upload,Deleted upload task") + if self.result['file_name'] == filename: + while (self.up_task_list[index]['is_destory'] == 0): + time.sleep(0.02) + del self.up_task_list[index] + self.uptask_lock.release() + return self.result - print("Finish upload,Deleted upload task") - - while (self.up_task_list[index]['is_destory'] == 0): - time.sleep(0.02) - - del self.up_task_list[index] - self.uptask_lock.release() - return self.result - elif (self.result['status'] == self.STATE_MQTT_UPLOAD_CANCEL_FAILED) or \ (self.result['status'] == self.STATE_MQTT_UPLOAD_FAILED) or \ (self.result['status'] == self.STATE_MQTT_UPLOAD_FAILED_TIMEOUT) or \ (self.result['status'] == self.STATE_MQTT_UPLOAD_FAILED_WHOLE_CHECK): - + self.up_task_list[index]['status'] = self.STATE_MQTT_UPLOAD_NONE self.result['err_code'] = self.up_task_list[index]['rsp_code'] - while (self.up_task_list[index]['is_destory'] == 0): - time.sleep(0.02) - del self.up_task_list[index] - self.uptask_lock.release() - return self.result - + + if self.result['file_name'] == filename: + while (self.up_task_list[index]['is_destory'] == 0): + time.sleep(0.02) + del self.up_task_list[index] + self.uptask_lock.release() + return self.result else: pass + self.uptask_lock.release() return self.result diff --git a/modules/ulinksdk/hashlib/__init__.py b/modules/ulinksdk/hashlib/__init__.py deleted file mode 100644 index c00f56afdb..0000000000 --- a/modules/ulinksdk/hashlib/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -try: - import uhashlib -except ImportError: - uhashlib = None - - -def init(): - for i in ("sha1", "sha224", "sha256"): - c = getattr(uhashlib, i, None) - if not c: - c = __import__("_" + i, None, None, (), 1) - c = getattr(c, i) - globals()[i] = c - - -init() - - -def new(algo, data=b""): - try: - c = globals()[algo] - return c(data) - except KeyError: - raise ValueError(algo) diff --git a/modules/ulinksdk/hashlib/_sha224.py b/modules/ulinksdk/hashlib/_sha224.py deleted file mode 100644 index a14b300def..0000000000 --- a/modules/ulinksdk/hashlib/_sha224.py +++ /dev/null @@ -1 +0,0 @@ -from ._sha256 import sha224 \ No newline at end of file diff --git a/modules/ulinksdk/hashlib/_sha256.py b/modules/ulinksdk/hashlib/_sha256.py deleted file mode 100644 index e4bdeca4e9..0000000000 --- a/modules/ulinksdk/hashlib/_sha256.py +++ /dev/null @@ -1,301 +0,0 @@ -SHA_BLOCKSIZE = 64 -SHA_DIGESTSIZE = 32 - - -def new_shaobject(): - return { - "digest": [0] * 8, - "count_lo": 0, - "count_hi": 0, - "data": [0] * SHA_BLOCKSIZE, - "local": 0, - "digestsize": 0, - } - - -ROR = lambda x, y: (((x & 0xFFFFFFFF) >> (y & 31)) | (x << (32 - (y & 31)))) & 0xFFFFFFFF -Ch = lambda x, y, z: (z ^ (x & (y ^ z))) -Maj = lambda x, y, z: (((x | y) & z) | (x & y)) -S = lambda x, n: ROR(x, n) -R = lambda x, n: (x & 0xFFFFFFFF) >> n -Sigma0 = lambda x: (S(x, 2) ^ S(x, 13) ^ S(x, 22)) -Sigma1 = lambda x: (S(x, 6) ^ S(x, 11) ^ S(x, 25)) -Gamma0 = lambda x: (S(x, 7) ^ S(x, 18) ^ R(x, 3)) -Gamma1 = lambda x: (S(x, 17) ^ S(x, 19) ^ R(x, 10)) - - -def sha_transform(sha_info): - W = [] - - d = sha_info["data"] - for i in range(0, 16): - W.append((d[4 * i] << 24) + (d[4 * i + 1] << 16) + (d[4 * i + 2] << 8) + d[4 * i + 3]) - - for i in range(16, 64): - W.append((Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]) & 0xFFFFFFFF) - - ss = sha_info["digest"][:] - - def RND(a, b, c, d, e, f, g, h, i, ki): - t0 = h + Sigma1(e) + Ch(e, f, g) + ki + W[i] - t1 = Sigma0(a) + Maj(a, b, c) - d += t0 - h = t0 + t1 - return d & 0xFFFFFFFF, h & 0xFFFFFFFF - - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 0, 0x428A2F98) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 1, 0x71374491) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 2, 0xB5C0FBCF) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 3, 0xE9B5DBA5) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 4, 0x3956C25B) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 5, 0x59F111F1) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 6, 0x923F82A4) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 7, 0xAB1C5ED5) - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 8, 0xD807AA98) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 9, 0x12835B01) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 10, 0x243185BE) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 11, 0x550C7DC3) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 12, 0x72BE5D74) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 13, 0x80DEB1FE) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 14, 0x9BDC06A7) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 15, 0xC19BF174) - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 16, 0xE49B69C1) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 17, 0xEFBE4786) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 18, 0x0FC19DC6) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 19, 0x240CA1CC) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 20, 0x2DE92C6F) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 21, 0x4A7484AA) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 22, 0x5CB0A9DC) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 23, 0x76F988DA) - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 24, 0x983E5152) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 25, 0xA831C66D) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 26, 0xB00327C8) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 27, 0xBF597FC7) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 28, 0xC6E00BF3) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 29, 0xD5A79147) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 30, 0x06CA6351) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 31, 0x14292967) - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 32, 0x27B70A85) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 33, 0x2E1B2138) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 34, 0x4D2C6DFC) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 35, 0x53380D13) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 36, 0x650A7354) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 37, 0x766A0ABB) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 38, 0x81C2C92E) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 39, 0x92722C85) - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 40, 0xA2BFE8A1) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 41, 0xA81A664B) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 42, 0xC24B8B70) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 43, 0xC76C51A3) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 44, 0xD192E819) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 45, 0xD6990624) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 46, 0xF40E3585) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 47, 0x106AA070) - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 48, 0x19A4C116) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 49, 0x1E376C08) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 50, 0x2748774C) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 51, 0x34B0BCB5) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 52, 0x391C0CB3) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 53, 0x4ED8AA4A) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 54, 0x5B9CCA4F) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 55, 0x682E6FF3) - ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 56, 0x748F82EE) - ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 57, 0x78A5636F) - ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 58, 0x84C87814) - ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 59, 0x8CC70208) - ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 60, 0x90BEFFFA) - ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 61, 0xA4506CEB) - ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 62, 0xBEF9A3F7) - ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 63, 0xC67178F2) - - dig = [] - for i, x in enumerate(sha_info["digest"]): - dig.append((x + ss[i]) & 0xFFFFFFFF) - sha_info["digest"] = dig - - -def sha_init(): - sha_info = new_shaobject() - sha_info["digest"] = [ - 0x6A09E667, - 0xBB67AE85, - 0x3C6EF372, - 0xA54FF53A, - 0x510E527F, - 0x9B05688C, - 0x1F83D9AB, - 0x5BE0CD19, - ] - sha_info["count_lo"] = 0 - sha_info["count_hi"] = 0 - sha_info["local"] = 0 - sha_info["digestsize"] = 32 - return sha_info - - -def sha224_init(): - sha_info = new_shaobject() - sha_info["digest"] = [ - 0xC1059ED8, - 0x367CD507, - 0x3070DD17, - 0xF70E5939, - 0xFFC00B31, - 0x68581511, - 0x64F98FA7, - 0xBEFA4FA4, - ] - sha_info["count_lo"] = 0 - sha_info["count_hi"] = 0 - sha_info["local"] = 0 - sha_info["digestsize"] = 28 - return sha_info - - -def getbuf(s): - if isinstance(s, str): - return s.encode("ascii") - else: - return bytes(s) - - -def sha_update(sha_info, buffer): - if isinstance(buffer, str): - raise TypeError("Unicode strings must be encoded before hashing") - count = len(buffer) - buffer_idx = 0 - clo = (sha_info["count_lo"] + (count << 3)) & 0xFFFFFFFF - if clo < sha_info["count_lo"]: - sha_info["count_hi"] += 1 - sha_info["count_lo"] = clo - - sha_info["count_hi"] += count >> 29 - - if sha_info["local"]: - i = SHA_BLOCKSIZE - sha_info["local"] - if i > count: - i = count - - # copy buffer - for x in enumerate(buffer[buffer_idx : buffer_idx + i]): - sha_info["data"][sha_info["local"] + x[0]] = x[1] - - count -= i - buffer_idx += i - - sha_info["local"] += i - if sha_info["local"] == SHA_BLOCKSIZE: - sha_transform(sha_info) - sha_info["local"] = 0 - else: - return - - while count >= SHA_BLOCKSIZE: - # copy buffer - sha_info["data"] = list(buffer[buffer_idx : buffer_idx + SHA_BLOCKSIZE]) - count -= SHA_BLOCKSIZE - buffer_idx += SHA_BLOCKSIZE - sha_transform(sha_info) - - # copy buffer - pos = sha_info["local"] - sha_info["data"][pos : pos + count] = list(buffer[buffer_idx : buffer_idx + count]) - sha_info["local"] = count - - -def sha_final(sha_info): - lo_bit_count = sha_info["count_lo"] - hi_bit_count = sha_info["count_hi"] - count = (lo_bit_count >> 3) & 0x3F - sha_info["data"][count] = 0x80 - count += 1 - if count > SHA_BLOCKSIZE - 8: - # zero the bytes in data after the count - sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) - sha_transform(sha_info) - # zero bytes in data - sha_info["data"] = [0] * SHA_BLOCKSIZE - else: - sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) - - sha_info["data"][56] = (hi_bit_count >> 24) & 0xFF - sha_info["data"][57] = (hi_bit_count >> 16) & 0xFF - sha_info["data"][58] = (hi_bit_count >> 8) & 0xFF - sha_info["data"][59] = (hi_bit_count >> 0) & 0xFF - sha_info["data"][60] = (lo_bit_count >> 24) & 0xFF - sha_info["data"][61] = (lo_bit_count >> 16) & 0xFF - sha_info["data"][62] = (lo_bit_count >> 8) & 0xFF - sha_info["data"][63] = (lo_bit_count >> 0) & 0xFF - - sha_transform(sha_info) - - dig = [] - for i in sha_info["digest"]: - dig.extend([((i >> 24) & 0xFF), ((i >> 16) & 0xFF), ((i >> 8) & 0xFF), (i & 0xFF)]) - return bytes(dig) - - -class sha256(object): - digest_size = digestsize = SHA_DIGESTSIZE - block_size = SHA_BLOCKSIZE - - def __init__(self, s=None): - self._sha = sha_init() - if s: - sha_update(self._sha, getbuf(s)) - - def update(self, s): - sha_update(self._sha, getbuf(s)) - - def digest(self): - return sha_final(self._sha.copy())[: self._sha["digestsize"]] - - def hexdigest(self): - return "".join(["%.2x" % i for i in self.digest()]) - - def copy(self): - new = sha256() - new._sha = self._sha.copy() - return new - - -class sha224(sha256): - digest_size = digestsize = 28 - - def __init__(self, s=None): - self._sha = sha224_init() - if s: - sha_update(self._sha, getbuf(s)) - - def copy(self): - new = sha224() - new._sha = self._sha.copy() - return new - - -def test(): - a_str = "just a test string" - - assert ( - b"\xe3\xb0\xc4B\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99o\xb9$'\xaeA\xe4d\x9b\x93L\xa4\x95\x99\x1bxR\xb8U" - == sha256().digest() - ) - assert ( - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" == sha256().hexdigest() - ) - assert ( - "d7b553c6f09ac85d142415f857c5310f3bbbe7cdd787cce4b985acedd585266f" - == sha256(a_str).hexdigest() - ) - assert ( - "8113ebf33c97daa9998762aacafe750c7cefc2b2f173c90c59663a57fe626f21" - == sha256(a_str * 7).hexdigest() - ) - - s = sha256(a_str) - s.update(a_str) - assert "03d9963e05a094593190b6fc794cb1a3e1ac7d7883f0b5855268afeccc70d461" == s.hexdigest() - - -if __name__ == "__main__": - test() diff --git a/modules/ulinksdk/hashlib/_sha384.py b/modules/ulinksdk/hashlib/_sha384.py deleted file mode 100644 index 4aebdc33e3..0000000000 --- a/modules/ulinksdk/hashlib/_sha384.py +++ /dev/null @@ -1 +0,0 @@ -from ._sha512 import sha384 \ No newline at end of file diff --git a/modules/ulinksdk/hashlib/_sha512.py b/modules/ulinksdk/hashlib/_sha512.py deleted file mode 100644 index 726fbb5f2d..0000000000 --- a/modules/ulinksdk/hashlib/_sha512.py +++ /dev/null @@ -1,519 +0,0 @@ -""" -This code was Ported from CPython's sha512module.c -""" - -SHA_BLOCKSIZE = 128 -SHA_DIGESTSIZE = 64 - - -def new_shaobject(): - return { - "digest": [0] * 8, - "count_lo": 0, - "count_hi": 0, - "data": [0] * SHA_BLOCKSIZE, - "local": 0, - "digestsize": 0, - } - - -ROR64 = ( - lambda x, y: (((x & 0xFFFFFFFFFFFFFFFF) >> (y & 63)) | (x << (64 - (y & 63)))) - & 0xFFFFFFFFFFFFFFFF -) -Ch = lambda x, y, z: (z ^ (x & (y ^ z))) -Maj = lambda x, y, z: (((x | y) & z) | (x & y)) -S = lambda x, n: ROR64(x, n) -R = lambda x, n: (x & 0xFFFFFFFFFFFFFFFF) >> n -Sigma0 = lambda x: (S(x, 28) ^ S(x, 34) ^ S(x, 39)) -Sigma1 = lambda x: (S(x, 14) ^ S(x, 18) ^ S(x, 41)) -Gamma0 = lambda x: (S(x, 1) ^ S(x, 8) ^ R(x, 7)) -Gamma1 = lambda x: (S(x, 19) ^ S(x, 61) ^ R(x, 6)) - - -def sha_transform(sha_info): - W = [] - - d = sha_info["data"] - for i in range(0, 16): - W.append( - (d[8 * i] << 56) - + (d[8 * i + 1] << 48) - + (d[8 * i + 2] << 40) - + (d[8 * i + 3] << 32) - + (d[8 * i + 4] << 24) - + (d[8 * i + 5] << 16) - + (d[8 * i + 6] << 8) - + d[8 * i + 7] - ) - - for i in range(16, 80): - W.append( - (Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]) & 0xFFFFFFFFFFFFFFFF - ) - - ss = sha_info["digest"][:] - - def RND(a, b, c, d, e, f, g, h, i, ki): - t0 = (h + Sigma1(e) + Ch(e, f, g) + ki + W[i]) & 0xFFFFFFFFFFFFFFFF - t1 = (Sigma0(a) + Maj(a, b, c)) & 0xFFFFFFFFFFFFFFFF - d = (d + t0) & 0xFFFFFFFFFFFFFFFF - h = (t0 + t1) & 0xFFFFFFFFFFFFFFFF - return d & 0xFFFFFFFFFFFFFFFF, h & 0xFFFFFFFFFFFFFFFF - - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 0, 0x428A2F98D728AE22 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 1, 0x7137449123EF65CD - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 2, 0xB5C0FBCFEC4D3B2F - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 3, 0xE9B5DBA58189DBBC - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 4, 0x3956C25BF348B538 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 5, 0x59F111F1B605D019 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 6, 0x923F82A4AF194F9B - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 7, 0xAB1C5ED5DA6D8118 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 8, 0xD807AA98A3030242 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 9, 0x12835B0145706FBE - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 10, 0x243185BE4EE4B28C - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 11, 0x550C7DC3D5FFB4E2 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 12, 0x72BE5D74F27B896F - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 13, 0x80DEB1FE3B1696B1 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 14, 0x9BDC06A725C71235 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 15, 0xC19BF174CF692694 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 16, 0xE49B69C19EF14AD2 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 17, 0xEFBE4786384F25E3 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 18, 0x0FC19DC68B8CD5B5 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 19, 0x240CA1CC77AC9C65 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 20, 0x2DE92C6F592B0275 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 21, 0x4A7484AA6EA6E483 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 22, 0x5CB0A9DCBD41FBD4 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 23, 0x76F988DA831153B5 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 24, 0x983E5152EE66DFAB - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 25, 0xA831C66D2DB43210 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 26, 0xB00327C898FB213F - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 27, 0xBF597FC7BEEF0EE4 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 28, 0xC6E00BF33DA88FC2 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 29, 0xD5A79147930AA725 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 30, 0x06CA6351E003826F - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 31, 0x142929670A0E6E70 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 32, 0x27B70A8546D22FFC - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 33, 0x2E1B21385C26C926 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 34, 0x4D2C6DFC5AC42AED - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 35, 0x53380D139D95B3DF - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 36, 0x650A73548BAF63DE - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 37, 0x766A0ABB3C77B2A8 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 38, 0x81C2C92E47EDAEE6 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 39, 0x92722C851482353B - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 40, 0xA2BFE8A14CF10364 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 41, 0xA81A664BBC423001 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 42, 0xC24B8B70D0F89791 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 43, 0xC76C51A30654BE30 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 44, 0xD192E819D6EF5218 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 45, 0xD69906245565A910 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 46, 0xF40E35855771202A - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 47, 0x106AA07032BBD1B8 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 48, 0x19A4C116B8D2D0C8 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 49, 0x1E376C085141AB53 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 50, 0x2748774CDF8EEB99 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 51, 0x34B0BCB5E19B48A8 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 52, 0x391C0CB3C5C95A63 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 53, 0x4ED8AA4AE3418ACB - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 54, 0x5B9CCA4F7763E373 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 55, 0x682E6FF3D6B2B8A3 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 56, 0x748F82EE5DEFB2FC - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 57, 0x78A5636F43172F60 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 58, 0x84C87814A1F0AB72 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 59, 0x8CC702081A6439EC - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 60, 0x90BEFFFA23631E28 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 61, 0xA4506CEBDE82BDE9 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 62, 0xBEF9A3F7B2C67915 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 63, 0xC67178F2E372532B - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 64, 0xCA273ECEEA26619C - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 65, 0xD186B8C721C0C207 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 66, 0xEADA7DD6CDE0EB1E - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 67, 0xF57D4F7FEE6ED178 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 68, 0x06F067AA72176FBA - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 69, 0x0A637DC5A2C898A6 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 70, 0x113F9804BEF90DAE - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 71, 0x1B710B35131C471B - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 72, 0x28DB77F523047D84 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 73, 0x32CAAB7B40C72493 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 74, 0x3C9EBE0A15C9BEBC - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 75, 0x431D67C49C100D4C - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 76, 0x4CC5D4BECB3E42B6 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 77, 0x597F299CFC657E2A - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 78, 0x5FCB6FAB3AD6FAEC - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 79, 0x6C44198C4A475817 - ) - - dig = [] - for i, x in enumerate(sha_info["digest"]): - dig.append((x + ss[i]) & 0xFFFFFFFFFFFFFFFF) - sha_info["digest"] = dig - - -def sha_init(): - sha_info = new_shaobject() - sha_info["digest"] = [ - 0x6A09E667F3BCC908, - 0xBB67AE8584CAA73B, - 0x3C6EF372FE94F82B, - 0xA54FF53A5F1D36F1, - 0x510E527FADE682D1, - 0x9B05688C2B3E6C1F, - 0x1F83D9ABFB41BD6B, - 0x5BE0CD19137E2179, - ] - sha_info["count_lo"] = 0 - sha_info["count_hi"] = 0 - sha_info["local"] = 0 - sha_info["digestsize"] = 64 - return sha_info - - -def sha384_init(): - sha_info = new_shaobject() - sha_info["digest"] = [ - 0xCBBB9D5DC1059ED8, - 0x629A292A367CD507, - 0x9159015A3070DD17, - 0x152FECD8F70E5939, - 0x67332667FFC00B31, - 0x8EB44A8768581511, - 0xDB0C2E0D64F98FA7, - 0x47B5481DBEFA4FA4, - ] - sha_info["count_lo"] = 0 - sha_info["count_hi"] = 0 - sha_info["local"] = 0 - sha_info["digestsize"] = 48 - return sha_info - - -def getbuf(s): - if isinstance(s, str): - return s.encode("ascii") - else: - return bytes(s) - - -def sha_update(sha_info, buffer): - if isinstance(buffer, str): - raise TypeError("Unicode strings must be encoded before hashing") - count = len(buffer) - buffer_idx = 0 - clo = (sha_info["count_lo"] + (count << 3)) & 0xFFFFFFFF - if clo < sha_info["count_lo"]: - sha_info["count_hi"] += 1 - sha_info["count_lo"] = clo - - sha_info["count_hi"] += count >> 29 - - if sha_info["local"]: - i = SHA_BLOCKSIZE - sha_info["local"] - if i > count: - i = count - - # copy buffer - for x in enumerate(buffer[buffer_idx : buffer_idx + i]): - sha_info["data"][sha_info["local"] + x[0]] = x[1] - - count -= i - buffer_idx += i - - sha_info["local"] += i - if sha_info["local"] == SHA_BLOCKSIZE: - sha_transform(sha_info) - sha_info["local"] = 0 - else: - return - - while count >= SHA_BLOCKSIZE: - # copy buffer - sha_info["data"] = list(buffer[buffer_idx : buffer_idx + SHA_BLOCKSIZE]) - count -= SHA_BLOCKSIZE - buffer_idx += SHA_BLOCKSIZE - sha_transform(sha_info) - - # copy buffer - pos = sha_info["local"] - sha_info["data"][pos : pos + count] = list(buffer[buffer_idx : buffer_idx + count]) - sha_info["local"] = count - - -def sha_final(sha_info): - lo_bit_count = sha_info["count_lo"] - hi_bit_count = sha_info["count_hi"] - count = (lo_bit_count >> 3) & 0x7F - sha_info["data"][count] = 0x80 - count += 1 - if count > SHA_BLOCKSIZE - 16: - # zero the bytes in data after the count - sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) - sha_transform(sha_info) - # zero bytes in data - sha_info["data"] = [0] * SHA_BLOCKSIZE - else: - sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) - - sha_info["data"][112] = 0 - sha_info["data"][113] = 0 - sha_info["data"][114] = 0 - sha_info["data"][115] = 0 - sha_info["data"][116] = 0 - sha_info["data"][117] = 0 - sha_info["data"][118] = 0 - sha_info["data"][119] = 0 - - sha_info["data"][120] = (hi_bit_count >> 24) & 0xFF - sha_info["data"][121] = (hi_bit_count >> 16) & 0xFF - sha_info["data"][122] = (hi_bit_count >> 8) & 0xFF - sha_info["data"][123] = (hi_bit_count >> 0) & 0xFF - sha_info["data"][124] = (lo_bit_count >> 24) & 0xFF - sha_info["data"][125] = (lo_bit_count >> 16) & 0xFF - sha_info["data"][126] = (lo_bit_count >> 8) & 0xFF - sha_info["data"][127] = (lo_bit_count >> 0) & 0xFF - - sha_transform(sha_info) - - dig = [] - for i in sha_info["digest"]: - dig.extend( - [ - ((i >> 56) & 0xFF), - ((i >> 48) & 0xFF), - ((i >> 40) & 0xFF), - ((i >> 32) & 0xFF), - ((i >> 24) & 0xFF), - ((i >> 16) & 0xFF), - ((i >> 8) & 0xFF), - (i & 0xFF), - ] - ) - return bytes(dig) - - -class sha512(object): - digest_size = digestsize = SHA_DIGESTSIZE - block_size = SHA_BLOCKSIZE - - def __init__(self, s=None): - self._sha = sha_init() - if s: - sha_update(self._sha, getbuf(s)) - - def update(self, s): - sha_update(self._sha, getbuf(s)) - - def digest(self): - return sha_final(self._sha.copy())[: self._sha["digestsize"]] - - def hexdigest(self): - return "".join(["%.2x" % i for i in self.digest()]) - - def copy(self): - new = sha512() - new._sha = self._sha.copy() - return new - - -class sha384(sha512): - digest_size = digestsize = 48 - - def __init__(self, s=None): - self._sha = sha384_init() - if s: - sha_update(self._sha, getbuf(s)) - - def copy(self): - new = sha384() - new._sha = self._sha.copy() - return new - - -def test(): - a_str = "just a test string" - - assert ( - sha512().digest() - == b"\xcf\x83\xe15~\xef\xb8\xbd\xf1T(P\xd6m\x80\x07\xd6 \xe4\x05\x0bW\x15\xdc\x83\xf4\xa9!\xd3l\xe9\xceG\xd0\xd1<]\x85\xf2\xb0\xff\x83\x18\xd2\x87~\xec/c\xb91\xbdGAz\x81\xa582z\xf9'\xda>" - ) - assert ( - sha512().hexdigest() - == "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" - ) - assert ( - sha512(a_str).hexdigest() - == "68be4c6664af867dd1d01c8d77e963d87d77b702400c8fabae355a41b8927a5a5533a7f1c28509bbd65c5f3ac716f33be271fbda0ca018b71a84708c9fae8a53" - ) - assert ( - sha512(a_str * 7).hexdigest() - == "3233acdbfcfff9bff9fc72401d31dbffa62bd24e9ec846f0578d647da73258d9f0879f7fde01fe2cc6516af3f343807fdef79e23d696c923d79931db46bf1819" - ) - - s = sha512(a_str) - s.update(a_str) - assert ( - s.hexdigest() - == "341aeb668730bbb48127d5531115f3c39d12cb9586a6ca770898398aff2411087cfe0b570689adf328cddeb1f00803acce6737a19f310b53bbdb0320828f75bb" - ) - - -if __name__ == "__main__": - test() diff --git a/modules/ulinksdk/hmac.py b/modules/ulinksdk/hmac.py deleted file mode 100644 index 83eded1d37..0000000000 --- a/modules/ulinksdk/hmac.py +++ /dev/null @@ -1,162 +0,0 @@ -"""HMAC (Keyed-Hashing for Message Authentication) Python module. - -Implements the HMAC algorithm as described by RFC 2104. -""" - -import warnings as _warnings - -# from _operator import _compare_digest as compare_digest -import hashlib as _hashlib - -PendingDeprecationWarning = None -RuntimeWarning = None - -trans_5C = bytes((x ^ 0x5C) for x in range(256)) -trans_36 = bytes((x ^ 0x36) for x in range(256)) - - -def translate(d, t): - return bytes(t[x] for x in d) - - -# The size of the digests returned by HMAC depends on the underlying -# hashing module used. Use digest_size from the instance of HMAC instead. -digest_size = None - - -class HMAC: - """RFC 2104 HMAC class. Also complies with RFC 4231. - - This supports the API for Cryptographic Hash Functions (PEP 247). - """ - - blocksize = 64 # 512-bit HMAC; can be changed in subclasses. - - def __init__(self, key, msg=None, digestmod=None): - """Create a new HMAC object. - - key: key for the keyed hash object. - msg: Initial input for the hash, if provided. - digestmod: A module supporting PEP 247. *OR* - A hashlib constructor returning a new hash object. *OR* - A hash name suitable for hashlib.new(). - Defaults to hashlib.md5. - Implicit default to hashlib.md5 is deprecated and will be - removed in Python 3.6. - - Note: key and msg must be a bytes or bytearray objects. - """ - - if not isinstance(key, (bytes, bytearray)): - raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) - - if digestmod is None: - _warnings.warn( - "HMAC() without an explicit digestmod argument " "is deprecated.", - PendingDeprecationWarning, - 2, - ) - digestmod = _hashlib.md5 - - if callable(digestmod): - self.digest_cons = digestmod - elif isinstance(digestmod, str): - self.digest_cons = lambda d=b"": _hashlib.new(digestmod, d) - else: - print('not callable') - self.digest_cons = lambda d=b"": digestmod.new(d) - - self.outer = self.digest_cons() - self.inner = self.digest_cons() - self.digest_size = self.inner.digest_size - - if hasattr(self.inner, "block_size"): - blocksize = self.inner.block_size - if blocksize < 16: - _warnings.warn( - "block_size of %d seems too small; using our " - "default of %d." % (blocksize, self.blocksize), - RuntimeWarning, - 2, - ) - blocksize = self.blocksize - else: - _warnings.warn( - "No block_size attribute on given digest object; " - "Assuming %d." % (self.blocksize), - RuntimeWarning, - 2, - ) - blocksize = self.blocksize - - # self.blocksize is the default blocksize. self.block_size is - # effective block size as well as the public API attribute. - self.block_size = blocksize - - if len(key) > blocksize: - key = self.digest_cons(key).digest() - - key = key + bytes(blocksize - len(key)) - self.outer.update(translate(key, trans_5C)) - self.inner.update(translate(key, trans_36)) - if msg is not None: - self.update(msg) - - @property - def name(self): - return "hmac-" + self.inner.name - - def update(self, msg): - """Update this hashing object with the string msg.""" - self.inner.update(msg) - - def copy(self): - """Return a separate copy of this hashing object. - - An update to this copy won't affect the original object. - """ - # Call __new__ directly to avoid the expensive __init__. - other = self.__class__.__new__(self.__class__) - other.digest_cons = self.digest_cons - other.digest_size = self.digest_size - other.inner = self.inner.copy() - other.outer = self.outer.copy() - return other - - def _current(self): - """Return a hash object for the current state. - - To be used only internally with digest() and hexdigest(). - """ - h = self.outer.copy() - h.update(self.inner.digest()) - return h - - def digest(self): - """Return the hash value of this hashing object. - - This returns a string containing 8-bit data. The object is - not altered in any way by this function; you can continue - updating the object after calling this function. - """ - h = self._current() - return h.digest() - - def hexdigest(self): - """Like digest(), but returns a string of hexadecimal digits instead.""" - h = self._current() - return h.hexdigest() - - -def new(key, msg=None, digestmod=None): - """Create a new hashing object and return it. - - key: The starting key for the hash. - msg: if available, will immediately be hashed into the object's starting - state. - - You can now feed arbitrary strings into the object using its update() - method, and can ask for the hash value at any time by calling its digest() - method. - """ - return HMAC(key, msg, digestmod) diff --git a/modules/ulinksdk/linksdk.py b/modules/ulinksdk/linksdk.py index a20ec6d0a0..3606662f7b 100644 --- a/modules/ulinksdk/linksdk.py +++ b/modules/ulinksdk/linksdk.py @@ -3,11 +3,9 @@ import _thread import hmac import uos -from _umqtt.simple2 import MQTTClient -from hashlib._sha256 import sha256 +from umqtt.simple2 import MQTTClient import urandom as random import time -from dynreg import DYNREG class LinkSDK: # topics 汇总 @@ -17,7 +15,7 @@ class LinkSDK: "prop_post_reply": "/sys/{}/{}/thing/event/property/post_reply", #.format(productKey, DeviceName), "prop_set": "/sys/{}/{}/thing/service/property/set", #.format(productKey, DeviceName), - # event + # event "event_post": "/sys/{}/{}/thing/event/{}/post", #.format(productKey, DeviceName, eventID), "event_post_reply": "/sys/{}/{}/thing/event/{}/post_reply", #.format(productKey, DeviceName, eventID), @@ -37,7 +35,7 @@ class LinkSDK: "ota_inform": "/ota/device/inform/{}/{}", #.format(productKey, DeviceName), "ota_upgrade": "/ota/device/upgrade/{}/${}", #.format(productKey, DeviceName), "ota_progress": "/ota/device/progress/{}/${}", #.format(productKey, DeviceName), - "get_ota_firmware": "/sys/{}/{}/thing/ota/firmware/get", #.format(productKey, DeviceName), + "get_ota_firmware": "/sys/{}/{}/thing/ota/firmware/get", #.format(productKey, DeviceName), /sys/+/+/thing/model/down_raw # Device Info "dev_info_update": "/sys/{}/{}/thing/deviceinfo/update", #.format(productKey, DeviceName), @@ -58,7 +56,7 @@ class LinkSDK: # broadcast "broadcast": "/broadcast/{}/{}", #.format(productKey, DeviceName), - # MQTT upload file topic + # MQTT upload file topic "upload_init": "/sys/{}/{}/thing/file/upload/mqtt/init", #.format(productKey, DeviceName) "upload_file": "/sys/{}/{}/thing/file/upload/mqtt/send", #.format(productKey, DeviceName) "upload_cancel": "/sys/{}/{}/thing/file/upload/mqtt/cancel", #.format(productKey, DeviceName) @@ -104,6 +102,8 @@ def __init__(self): self.sub_list_lock = _thread.allocate_lock() + self.exit = False + def global_alink_id_next(self): self.g_global_lock.acquire() self.alink_id = self.alink_id+1 @@ -112,16 +112,28 @@ def global_alink_id_next(self): self.g_global_lock.release() return self.alink_id - def lk_register(self, deviceinfo, callback): - print("in lk_register..") - self.dynreg_handle = DYNREG(lk_handle = self, deviceinfo = deviceinfo, cb = callback) - print("in lk_register001..") - - self.dynreg_handle.dynreg_send_request() + def __check_secret(self, deviceName): + try: + with open("/secret.json", "r", encoding="utf-8") as f: + secret_data = json.load(f) + except Exception as e: + print("[ERROR] Device Secret File Open failed : %s" % str(e)) + device_secret = secret_data.get(deviceName, None) + if device_secret != None: + return True + return False - self.dynreg_handle.dynreg_recv() + def lk_register(self, deviceinfo, callback): + if "secret.json" in uos.listdir("/"): + print("found secret file") + if self.__check_secret(deviceinfo['deviceName']): + callback("[INFO] device is already activated") + return 0 + if self.dynreg_handle == None: + from ulinksdk.dynreg import DYNREG + self.dynreg_handle = DYNREG() + return self.dynreg_handle.register(deviceinfo,callback) - pass def lk_connect(self,key_info): # param check @@ -135,7 +147,7 @@ def lk_connect(self,key_info): raise ValueError("device secret & product secret are both empty!") if 'keepaliveSec' not in key_info: raise ValueError("keep alive wrong!") - + self.region = key_info['region'] self.productKey = key_info['productKey'] self.deviceName = key_info['deviceName'] @@ -146,141 +158,112 @@ def lk_connect(self,key_info): self.keepAlive = 300 #self.keepAlive = key_info['keepaliveSec'] self.username = "{}&{}".format(self.deviceName, self.productKey) - #self.__connect_main() + self.__connect_main() + if self.connected == True: + postProps_node = self.__create_subscribe_node(self.TOPIC["prop_set"].format(self.productKey, self.deviceName), + self.oncallback[self.ON_PROPS]) + self.__subsribe_list_insert(postProps_node) - _thread.start_new_thread(self.__connect_main, ()) + #_thread.start_new_thread(self.__connect_main, ()) _thread.start_new_thread(self.__listen, ()) - #self.__start() + return 0 + def __subsribe_list_insert(self, listnode): self.sub_list_lock.acquire() + for index in range(len(self.subscribe_list)): + if self.subscribe_list[index]['topic'] == listnode['topic']: + self.subscribe_list[index]['handler'] = listnode['handler'] + self.sub_list_lock.release() + return self.subscribe_list.append(listnode) self.sub_list_lock.release() + return - def lk_rundom(self): - msg = "" - for i in range(0,5): - num = random.randint(1, 10) - msg += str(num) - return msg - - def lk_check_secret(self, deviceName): - try: - with open("/data/pyamp/secret.json", "r", encoding="utf-8") as f: - secret_data = json.load(f) - except Exception as e: - print("[ERROR] File Open failed : %s" % str(e)) - device_secret = secret_data.get(deviceName, None) - if device_secret != None: - return device_secret - return False - - def lk_save_Secret(self, data): - secret_data = {} - if "secret.json" in uos.listdir("data/pyamp"): - with open("/data/pyamp/secret.json", "r", encoding="utf-8") as f: - secret_data = json.load(f) - print(secret_data) - try: - with open("/data/pyamp/secret.json", "w+", encoding="utf-8") as w: - secret_data.update(data) - json.dump(secret_data, w) - except Exception as e: - print("[ERROR] File write failed : %s" % str(e)) - - def __formatConnectInfo(self, secret, randomNum=None): - secret = secret - if randomNum != None: - mqt_id = "{}|securemode=2,authType=register,random={},signmethod=hmacsha256|".format(self.clientID, randomNum) - hmac_msg = "deviceName{}productKey{}random{}".format(self.deviceName, self.productKey, randomNum) - else: - mqt_id = "{}|securemode=3,signmethod=hmacsha256|".format(self.clientID) - hmac_msg = "clientId{}deviceName{}productKey{}".format(self.clientID, self.deviceName, self.productKey) + def __formatConnectInfo(self, deviceSecret): + secret = deviceSecret + mqt_id = "{}|securemode=3,signmethod=hmacsha256|".format(self.clientID) + hmac_msg = "clientId{}deviceName{}productKey{}".format(self.clientID, self.deviceName, self.productKey) return mqt_id, secret, hmac_msg def __default_recv_handler(self, topic, msg, retained, status): - print("Subscribe Recv: Topic={},Msg={}".format(topic.decode(), msg.decode())) - if str(topic, "utf-8") == "/ext/register": - data = json.loads(msg) - self.deviceSecret = data.get("deviceSecret") - self.productSecret = None - data = {self.deviceName: self.deviceSecret} - self.lk_save_Secret(data) # save DeviceSecret - #self.__connect_main() + # print("Subscribe Recv: Topic={},Msg={}".format(topic.decode(), msg.decode())) + try: + msg_data = json.loads(msg.decode()) + except: + msg_data = msg + self.sub_list_lock.acquire() + for node in self.subscribe_list: + if node['topic'] == topic.decode(): + if (node['handler'] != None): + node['handler'](msg_data) + break + self.sub_list_lock.release() + service_topic = "/sys/+/+/thing/service/+" + if (self.__compare_topic(service_topic, topic.decode())): + if (self.oncallback[self.ON_SERVICE] != None): + self.oncallback[self.ON_SERVICE](msg_data) + + def __compare_topic(self, topic01, topic02): + topic01_split = topic01.split('/') + topic02_split = topic02.split('/') + topic01_len = len(topic01_split) + topic02_len = len(topic02_split) + if (topic01_len == topic02_len): + for i in range(topic01_len): + if (topic01_split[i] != topic02_split[i]) and (topic01_split[i] != '+'): + return False + return True else: - self.sub_list_lock.acquire() - for node in self.subscribe_list: - if node['topic'] == topic.decode(): - if node['handler'] != None: - msg_data = json.loads(msg.decode()) - node['handler'](msg_data) - self.sub_list_lock.release() - + return False + def __connect_main(self): - if self.productSecret == None: - ssl = False - mqt_id, secret, hmac_msg = self.__formatConnectInfo(self.deviceSecret) - print(secret) - print(hmac_msg) - else: - if "secret.json" in uos.listdir("usr/"): - msg = self.lk_check_secret(self.deviceName) - if msg: - self.deviceSecret = msg - mqt_id, secret, hmac_msg = self.__formatConnectInfo(self.deviceSecret) - self.__mqtt_connect(mqt_id, secret, hmac_msg, self.keepAlive, self.clean_session, ssl=False) - print("[INFO] The MQTT connection was successful") - return 0 - print("[INFO] MQTT dynamic registration") - ssl = True - randomNum = self.lk_rundom() - mqt_id, secret, hmac_msg = self.__formatConnectInfo(self.productSecret, randomNum=randomNum) - try: - mqtts_cl = self.__mqtt_connect(mqt_id, secret, hmac_msg, self.keepAlive, self.clean_session, ssl) - except: - return -1 - utime.sleep(2) - mqtts_cl.wait_msg() - utime.sleep(1) - mqtts_cl.disconnect() - return 0 - try: + ssl = False + mqt_id, secret, hmac_msg = self.__formatConnectInfo(self.deviceSecret) + self.__mqtt_connect(mqt_id, secret, hmac_msg, self.keepAlive, self.clean_session, ssl) + '''try: self.__mqtt_connect(mqt_id, secret, hmac_msg, self.keepAlive, self.clean_session, ssl) - return 0 except: - return -1 + print("lk mqtt connect error")''' def __listen(self): - print('start listen...') + print('listen thread start') while self.connected == False: # wait for mqtt connect success - pass - while self.connected == True: + utime.sleep(0.5) + while True: + if self.exit == True: + print('listen thread exit') + _thread.exit() try: - self.mqtt_client.wait_msg() - except OSError as e: - return -1 + self.mqtt_client.wait_msg_timeout() + except: + pass def __mqtt_connect(self, mqt_id, secret, hmac_msg, keepAlive, clean_session, ssl): - print('start mqtt connect...') + #print('start mqtt connect...') mqt_server = "{}.iot-as-mqtt.{}.aliyuncs.com".format(self.productKey, self.region) - self.password = hmac.new(bytes(secret, "utf8"), msg=bytes(hmac_msg, "utf8"), digestmod=sha256).hexdigest() - #self.password = "52b84e97a779bf69b2cf57178d8c58980edbb79f8c568af8e1c9d1df38f17520" + #self.password = _hmac.new(bytes(secret, "utf8"), msg=bytes(hmac_msg, "utf8"), digestmod=sha256).hexdigest() + self.password = hmac.sha256(secret, hmac_msg) + '''print("secret:", secret) + print("hmac_msg:", hmac_msg) + old_pwd = "c8418b54579b0c25cb6aff55359076ab6ca789eb8b855c35f16fe37ca30abc01" + print("old pwd is:", old_pwd) + print("new pwd is:",self.password)''' mqtt_client = MQTTClient(mqt_id, mqt_server, self.port, self.username, self.password, keepAlive, ssl=ssl) mqtt_client.set_callback(self.__default_recv_handler) mqtt_client.connect(clean_session=clean_session) self.mqtt_client = mqtt_client # this must be set before setting self.connected self.connected = True - print('mqtt connect success...') + #print('mqtt connect success...') if (self.oncallback[self.ON_CONNECT] != None): self.oncallback[self.ON_CONNECT](self.connected) - return self.mqtt_client def __rev_NTPtime_handler(self, data): dst = int(data['deviceSendTime']) srt = int(data['serverRecvTime']) - sst = int(data['serverSendTime']) + sst = int(data['serverSendTime']) utc = int((srt + sst + int(time.time()*1000) - dst) / 2) NTPtime = dict() NTPtime['msecond'] = utc % 1000 @@ -313,7 +296,7 @@ def lk_getDeviceInfo(self): return devinfo def lk_subscribe(self, topic, qos=0): - + subscribe_node = self.__create_subscribe_node(topic, self.oncallback[self.ON_SUBCRIBE]) self.__subsribe_list_insert(subscribe_node) @@ -332,7 +315,6 @@ def lk_unsubscribe(self, topic, qos=0): print("[WARNING] unsubscribe failed. Try to reconnect : %s" % str(e)) return -1 - def lk_publish(self, topic, msg, retain=False, qos=0): try: self.mqtt_client.publish(topic, msg, retain, qos) @@ -342,23 +324,19 @@ def lk_publish(self, topic, msg, retain=False, qos=0): return -1 def lk_postProps(self, post_data): - postProps_node = self.__create_subscribe_node(self.TOPIC["prop_set"].format(self.productKey, self.deviceName), - self.oncallback[self.ON_PROPS]) - self.__subsribe_list_insert(postProps_node) post_payload = post_data['params'] - msg_dic = dict() msg_dic['id'] = str(self.global_alink_id_next()) msg_dic['version'] = "1.0" - msg_dic['params'] = post_payload + msg_dic['params'] = json.loads(post_payload) msg_dic['sys'] = {"ack": "1"} msg = json.dumps(msg_dic) - res = self.lk_publish(self.TOPIC["prop_post"].format(self.productKey, self.deviceName), msg) + res = self.lk_publish(self.TOPIC["prop_post"].format(self.productKey, self.deviceName), msg.encode('utf-8')) return res def lk_postEvent(self, event_data): event_id = str(event_data['id']) - event_payload = event_data['params'] + event_payload = json.loads(event_data['params']) msg_dic =dict() msg_dic['id'] = str(self.global_alink_id_next()) @@ -367,33 +345,34 @@ def lk_postEvent(self, event_data): msg_dic['sys'] = {"ack": "1"} msg = json.dumps(msg_dic) - postEvent_node = self.__create_subscribe_node(self.TOPIC["event_post_reply"].format(self.productKey, self.deviceName, event_id), + postEvent_node = self.__create_subscribe_node(self.TOPIC["event_post_reply"].format(self.productKey, self.deviceName, event_id), self.oncallback[self.ON_EVENT]) self.__subsribe_list_insert(postEvent_node) - res = self.lk_publish(self.TOPIC["event_post"].format(self.productKey, self.deviceName, event_id), msg) + res = self.lk_publish(self.TOPIC["event_post"].format(self.productKey, self.deviceName, event_id), msg.encode('utf-8')) return res - + def lk_postRaw(self, rawdata): msg = rawdata['param'] - - postRaw_node = self.__create_subscribe_node(self.TOPIC['model_up_reply'].format(self.productKey, self.deviceName), + + postRaw_node = self.__create_subscribe_node(self.TOPIC['model_down'].format(self.productKey, self.deviceName), self.oncallback[self.ON_RAWDATA]) self.__subsribe_list_insert(postRaw_node) return self.lk_publish(self.TOPIC["model_up"].format(self.productKey, self.deviceName), msg) - def lk_dis_connect(self): - self.mqtt_client.disconnect() - self.connected = False + def lk_disconnect(self): + if (self.connected == True): + self.mqtt_client.disconnect() + self.connected = False if (self.oncallback[self.ON_DISCONNECT] != None): - self.oncallback[self.ON_DISCONNECT]() - pass + self.oncallback[self.ON_DISCONNECT](0) + utime.sleep(1) def lk_set_oncallback(self, oncb): self.oncallback= oncb - + def lk_getNTPtime(self, cb): self.ntpcallback = cb ntptime_node = self.__create_subscribe_node(self.TOPIC['ntp_res'].format(self.productKey, self.deviceName), @@ -414,24 +393,14 @@ def get_rand(self): def lk_upload(self, filename, data, data_len, cb): if filename == None: raise ValueError("filename wrong!") - - from upload_file import UPload - self.up_handle = UPload(lk_handle= self) - - self.up_handle.upload_subsribe() - - if data_len == 0: - self.up_handle.g_data = None - self.up_handle.g_data_len = 0 - self.up_handle.g_file_path = data - file_len = self.up_handle.get_file_size(data) - else: - self.up_handle.g_data = data - self.up_handle.g_data_len = data_len - file_len = data_len + from ulinksdk.fileupload import UPload + if self.up_handle == None: + self.up_handle = UPload(lk_handle = self) + self.up_handle.upload_subsribe() - # print("file size is", file_len) #test point + file_len = self.up_handle.file_data_desc_insert(filename, data, data_len) + #print("file size is", file_len) #test point file_option = dict() file_option['file_name'] = filename file_option['file_size'] = file_len @@ -451,41 +420,37 @@ def lk_upload(self, filename, data, data_len, cb): # print('found == 0??', found) #test point if found == 0: - crc64_str = '\0' * 32 # without CRC + crc64_str = '\0' * 32 # without CRC uuid = (str(int(time.time() * 1000))[0:13] + str(self.get_rand()))[0:15] # print('uuid:',uuid) #test point up_task_new = self.up_handle.upload_file_task_init(file_option['file_name'], file_option['file_size'], \ file_option['mode'] ,crc64_str, uuid, file_option['read_data_handler'], file_option['userdata']) - + self.up_handle.upload_file_task_insert(up_task_new) self.up_handle.uptask_lock.release() - + self.up_handle.upload_open_stream(filename, None) result = dict() while True: - result = self.up_handle.upload_process() - if (result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FINISHED): - print( "MQTT Upload file({}) ID({}) success".format(result['file_name'], result['uploadid'])) - break - - elif result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FAILED or \ - result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FAILED_TIMEOUT or \ - result['status'] == self.up_handle.STATE_MQTT_UPLOAD_CANCEL_FAILED : - - print("MQTT Upload file({}) failed,res:-0x{}".format(result['file_name'], str(0-result['status'])) ) - break - - elif result['status'] == self.up_handle.STATE_MQTT_UPLOAD_CANCEL_SUCCESS: - print("MQTT Upload file({}) cancel success,res:-0x{}".format(result['file_name'], str(0-result['status']))) - break - - elif result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FAILED_WHOLE_CHECK: - print("MQTT Upload file({}) whole file md5 failed,res:-0x{}".format(result['file_name'], str(0-result['status']))) - break - - if result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FINISHED: + result = self.up_handle.upload_process(filename) + if result['file_name'] == filename: + if (result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FINISHED) : + #print( "MQTT Upload file({}) ID({}) success".format(result['file_name'], result['uploadid'])) + break + elif result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FAILED or \ + result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FAILED_TIMEOUT or \ + result['status'] == self.up_handle.STATE_MQTT_UPLOAD_CANCEL_FAILED : + print("MQTT Upload file({}) failed,res:-{}".format(result['file_name'], hex(0-result['status'])) ) + break + elif result['status'] == self.up_handle.STATE_MQTT_UPLOAD_CANCEL_SUCCESS: + #print("MQTT Upload file({}) cancel success,res:-{}".format(result['file_name'], hex(0-result['status']))) + break + elif result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FAILED_WHOLE_CHECK: + print("MQTT Upload file({}) whole file md5 failed,res:-{}".format(result['file_name'], hex(0-result['status']))) + break + + if (result['status'] == self.up_handle.STATE_MQTT_UPLOAD_FINISHED): return result['uploadid'] else: return None - \ No newline at end of file diff --git a/modules/ulinksdk/main.py b/modules/ulinksdk/main.py deleted file mode 100644 index 33b89b2bfd..0000000000 --- a/modules/ulinksdk/main.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- - -import utime # 延时函数在utime库中 -import network # Wi-Fi功能所在库 -from aliyunIoT_test import Device -import json - -# Wi-Fi SSID和Password设置 -wifiSsid = "请填写您的路由器名称" -wifiPassword = "请填写您的路由器密码" - -productKey = "产品密钥" #需要填入物联网云平台申请到的productKey信息 -deviceName = "设备名称" #需要填入物联网云平台申请到的deviceName信息 -deviceSecret = "设备密码" #需要填入物联网云平台申请到的deviceSecret信息 -ProductSecret = "产品密码" #需要填入物联网云平台申请到的productSecret信息 - -iot_connected = False -ntp_time_received = True -g_register_flag = False - -# 等待Wi-Fi成功连接到路由器 -def get_wifi_status(): - global wlan - wifi_connected = False - - wlan = network.WLAN(network.STA_IF) #创建WLAN对象 - wifi_connected = wlan.isconnected() # 获取Wi-Fi连接路由器的状态信息 - if not wifi_connected: - wlan.active(True) #激活界面 - wlan.scan() #扫描接入点 - #print("start to connect ", wifiSsid) - wlan.connect(wifiSsid, wifiPassword) # 连接到指定的路由器(路由器名称为wifiSsid, 密码为:wifiPassword) - - while True: - wifi_connected = wlan.isconnected() # 获取Wi-Fi连接路由器的状态信息 - if wifi_connected: # Wi-Fi连接成功则退出while循环 - break - else: - utime.sleep(0.5) - print("wifi_connected:", wifi_connected) - - ifconfig = wlan.ifconfig() #获取接口的IP/netmask/gw/DNS地址 - print(ifconfig) - utime.sleep(0.5) - -def cb_lk_register(data): - global g_register_flag - print("device regiter succeed !") - g_register_flag = True - -def on_connect(data): - global iot_connected - iot_connected = True - -def ntp_cb(data): - global ntp_time_received - - print('%s-%s-%s %s:%s:%s:%s'%(data['year'],data['month'],\ - data['day'],data['hour'],\ - data['minute'],data['second'],data['msecond'])) - print('timestamp:', data['timestamp']) - ntp_time_received = True - -def on_props(msg): - print("property set received!") - payload= msg['params'] - if 'LightSwitch' in payload: - print('LightSwitch: ', payload['LightSwitch']) - -def start_tests(): - global productKey, deviceName, deviceSecret, iot_connected - - key_info = { - 'region': 'cn-shanghai', - 'productKey': productKey, - 'deviceName': deviceName, - 'deviceSecret': deviceSecret, - 'keepaliveSec': 60 - } - - dev_register_info = { - 'deviceName': deviceName, - 'productKey': productKey, - 'productSecret': ProductSecret, - 'region': 'cn-shanghai', - } - - td_r = Device() - - '''print('start register test...') - td_r.register(dev_register_info, cb_lk_register) - i = 0 - while g_register_flag == False: - utime.sleep(0.5) - print("waitng for request success...") - i = i+1 - if (i > 20): - print("requst failed...") - break - - if g_register_flag == True: - print('register test pass!') - else: - print('register test fail!') - - del td_r''' - - td = Device() - td.on(td.ON_CONNECT, on_connect) - td.connect(key_info) - while (iot_connected == False): # wait for connect success - print('wait for connect success...') - utime.sleep(0.5) - pass - - print('start post property test...') - utime.sleep(1) - value = {'test_prop' : 100} - data = {'params': json.dumps(value)} - td.on(Device.ON_PROPS, on_props) - - ret = td.postProps(data) - if ret == 0 : - print('post property test pass!') - else : - print('post property test fail!') - utime.sleep(2) - - print('start post event test...') - utime.sleep(1) - value = {'test_event' : 100} - param_str = json.dumps(value) - data = {'id': 'EventTest' ,'params': param_str} - ret = td.postEvent(data) - if ret == 0 : - print('post event test pass!') - else : - print('post event test fail!') - utime.sleep(2) - - print('start post Raw test...') - utime.sleep(1) - usertestdata = bytearray([0x00,0x00,0x00,0x00,0x01,0x00,0x32,0x01,0x00,0x00,0x00,0x00]) - postdata={'param':usertestdata} - ret = td.postRaw(postdata) - if ret == 0 : - print('post Raw test pass!') - else : - print('post Raw test fail!') - - print('start getNtpTime test...') - time = td.getNtpTime(ntp_cb) - if ntp_time_received == True: - print('getNtpTime test pass!') - else : - print('getNtpTime test fail!') - - utime.sleep(2) - - print('start uploadfile test...') - with open('test_local.txt', "w+") as f: - f.write("hello world from vic") - - res = td.uploadFile( 'test_up.txt', 'test_local.txt', None) - if res != None: - print('uploadfile test pass!, uploadid:', res) - else: - print('uploadfile test fail!, uploadid:', res) - - print('start uploadcontent test...') - f = open('test_local.txt', "w+") - f.write("hello world again from vic") - f.seek(0) - content = f.read() - f.close() - res = td.uploadContent( 'test_up.txt', content, None) - if res != None: - print('uploadcontent test pass!, uploadid:', res) - else: - print('uploadcontent test fail!, uploadid:', res) - utime.sleep(2) - - print('start subscribe test...') - topic_test_info = { - 'topic': "/sys/{}/{}/thing/deviceinfo/update_reply".format(productKey, deviceName), - 'qos': 1 - } - res = td.subscribe(topic_test_info) - if res == 0: - print('subscribe test pass!') - else : - print('subscribe test fail!') - utime.sleep(2) - - print('start unsubscribe test...') - topic_test_info = { - 'topic': "/sys/{}/{}/thing/deviceinfo/update_reply".format(productKey, deviceName), - 'qos': 1 - } - res = td.unsubscribe(topic_test_info) - if res == 0: - print('unsubscribe test pass!') - else : - print('unsubscribe test fail!') - utime.sleep(2) - - td.end() - -if __name__ == '__main__': - print("hello python linksdk") - get_wifi_status() - utime.sleep(1) - start_tests() \ No newline at end of file diff --git a/modules/ulinksdk/manifest.py b/modules/ulinksdk/manifest.py new file mode 100644 index 0000000000..13233f88da --- /dev/null +++ b/modules/ulinksdk/manifest.py @@ -0,0 +1,8 @@ +freeze( + "..", + ( + "ulinksdk/dynreg.py", + "ulinksdk/fileupload.py", + "ulinksdk/linksdk.py", + ), +) diff --git a/modules/ulinksdk/urllib/__init__.py b/modules/ulinksdk/urllib/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/modules/ulinksdk/urllib/parse.py b/modules/ulinksdk/urllib/parse.py deleted file mode 100644 index 709cc578f5..0000000000 --- a/modules/ulinksdk/urllib/parse.py +++ /dev/null @@ -1,1150 +0,0 @@ -"""Parse (absolute and relative) URLs. - -urlparse module is based upon the following RFC specifications. - -RFC 3986 (STD66): "Uniform Resource Identifiers" by T. Berners-Lee, R. Fielding -and L. Masinter, January 2005. - -RFC 2732 : "Format for Literal IPv6 Addresses in URL's by R.Hinden, B.Carpenter -and L.Masinter, December 1999. - -RFC 2396: "Uniform Resource Identifiers (URI)": Generic Syntax by T. -Berners-Lee, R. Fielding, and L. Masinter, August 1998. - -RFC 2368: "The mailto URL scheme", by P.Hoffman , L Masinter, J. Zawinski, July 1998. - -RFC 1808: "Relative Uniform Resource Locators", by R. Fielding, UC Irvine, June -1995. - -RFC 1738: "Uniform Resource Locators (URL)" by T. Berners-Lee, L. Masinter, M. -McCahill, December 1994 - -RFC 3986 is considered the current standard and any future changes to -urlparse module should conform with it. The urlparse module is -currently not entirely compliant with this RFC due to defacto -scenarios for parsing, and for backward compatibility purposes, some -parsing quirks from older RFCs are retained. The testcases in -test_urlparse.py provides a good indicator of parsing behavior. -""" - -import re -import sys -import collections - -__all__ = [ - "urlparse", - "urlunparse", - "urljoin", - "urldefrag", - "urlsplit", - "urlunsplit", - "urlencode", - "parse_qs", - "parse_qsl", - "quote", - "quote_plus", - "quote_from_bytes", - "unquote", - "unquote_plus", - "unquote_to_bytes", -] - -# A classification of schemes ('' means apply by default) -uses_relative = [ - "ftp", - "http", - "gopher", - "nntp", - "imap", - "wais", - "file", - "https", - "shttp", - "mms", - "prospero", - "rtsp", - "rtspu", - "", - "sftp", - "svn", - "svn+ssh", -] -uses_netloc = [ - "ftp", - "http", - "gopher", - "nntp", - "telnet", - "imap", - "wais", - "file", - "mms", - "https", - "shttp", - "snews", - "prospero", - "rtsp", - "rtspu", - "rsync", - "", - "svn", - "svn+ssh", - "sftp", - "nfs", - "git", - "git+ssh", -] -uses_params = [ - "ftp", - "hdl", - "prospero", - "http", - "imap", - "https", - "shttp", - "rtsp", - "rtspu", - "sip", - "sips", - "mms", - "", - "sftp", - "tel", -] - -# These are not actually used anymore, but should stay for backwards -# compatibility. (They are undocumented, but have a public-looking name.) -non_hierarchical = [ - "gopher", - "hdl", - "mailto", - "news", - "telnet", - "wais", - "imap", - "snews", - "sip", - "sips", -] -uses_query = [ - "http", - "wais", - "imap", - "https", - "shttp", - "mms", - "gopher", - "rtsp", - "rtspu", - "sip", - "sips", - "", -] -uses_fragment = [ - "ftp", - "hdl", - "http", - "gopher", - "news", - "nntp", - "wais", - "https", - "shttp", - "snews", - "file", - "prospero", - "", -] - -# Characters valid in scheme names -scheme_chars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "+-." - -# XXX: Consider replacing with functools.lru_cache -MAX_CACHE_SIZE = 20 -_parse_cache = {} - - -def clear_cache(): - """Clear the parse cache and the quoters cache.""" - _parse_cache.clear() - _safe_quoters.clear() - - -# Helpers for bytes handling -# For 3.2, we deliberately require applications that -# handle improperly quoted URLs to do their own -# decoding and encoding. If valid use cases are -# presented, we may relax this by using latin-1 -# decoding internally for 3.3 -_implicit_encoding = "ascii" -_implicit_errors = "strict" - - -def _noop(obj): - return obj - - -def _encode_result(obj, encoding=_implicit_encoding, errors=_implicit_errors): - return obj.encode(encoding, errors) - - -def _decode_args(args, encoding=_implicit_encoding, errors=_implicit_errors): - return tuple(x.decode(encoding, errors) if x else "" for x in args) - - -def _coerce_args(*args): - # Invokes decode if necessary to create str args - # and returns the coerced inputs along with - # an appropriate result coercion function - # - noop for str inputs - # - encoding function otherwise - str_input = isinstance(args[0], str) - for arg in args[1:]: - # We special-case the empty string to support the - # "scheme=''" default argument to some functions - if arg and isinstance(arg, str) != str_input: - raise TypeError("Cannot mix str and non-str arguments") - if str_input: - return args + (_noop,) - return _decode_args(args) + (_encode_result,) - - -# Result objects are more helpful than simple tuples -class _ResultMixinStr(object): - """Standard approach to encoding parsed results from str to bytes""" - - __slots__ = () - - def encode(self, encoding="ascii", errors="strict"): - return self._encoded_counterpart(*(x.encode(encoding, errors) for x in self)) - - -class _ResultMixinBytes(object): - """Standard approach to decoding parsed results from bytes to str""" - - __slots__ = () - - def decode(self, encoding="ascii", errors="strict"): - return self._decoded_counterpart(*(x.decode(encoding, errors) for x in self)) - - -class _NetlocResultMixinBase(object): - """Shared methods for the parsed result objects containing a netloc element""" - - __slots__ = () - - @property - def username(self): - return self._userinfo[0] - - @property - def password(self): - return self._userinfo[1] - - @property - def hostname(self): - hostname = self._hostinfo[0] - if not hostname: - hostname = None - elif hostname is not None: - hostname = hostname.lower() - return hostname - - @property - def port(self): - port = self._hostinfo[1] - if port is not None: - port = int(port, 10) - # Return None on an illegal port - if not (0 <= port <= 65535): - return None - return port - - -class _NetlocResultMixinStr(_NetlocResultMixinBase, _ResultMixinStr): - __slots__ = () - - @property - def _userinfo(self): - netloc = self.netloc - userinfo, have_info, hostinfo = netloc.rpartition("@") - if have_info: - username, have_password, password = userinfo.partition(":") - if not have_password: - password = None - else: - username = password = None - return username, password - - @property - def _hostinfo(self): - netloc = self.netloc - _, _, hostinfo = netloc.rpartition("@") - _, have_open_br, bracketed = hostinfo.partition("[") - if have_open_br: - hostname, _, port = bracketed.partition("]") - _, have_port, port = port.partition(":") - else: - hostname, have_port, port = hostinfo.partition(":") - if not have_port: - port = None - return hostname, port - - -class _NetlocResultMixinBytes(_NetlocResultMixinBase, _ResultMixinBytes): - __slots__ = () - - @property - def _userinfo(self): - netloc = self.netloc - userinfo, have_info, hostinfo = netloc.rpartition(b"@") - if have_info: - username, have_password, password = userinfo.partition(b":") - if not have_password: - password = None - else: - username = password = None - return username, password - - @property - def _hostinfo(self): - netloc = self.netloc - _, _, hostinfo = netloc.rpartition(b"@") - _, have_open_br, bracketed = hostinfo.partition(b"[") - if have_open_br: - hostname, _, port = bracketed.partition(b"]") - _, have_port, port = port.partition(b":") - else: - hostname, have_port, port = hostinfo.partition(b":") - if not have_port: - port = None - return hostname, port - - -from collections import namedtuple - -_DefragResultBase = namedtuple("DefragResult", "url fragment") -_SplitResultBase = namedtuple("SplitResult", "scheme netloc path query fragment") -_ParseResultBase = namedtuple("ParseResult", "scheme netloc path params query fragment") - -# For backwards compatibility, alias _NetlocResultMixinStr -# ResultBase is no longer part of the documented API, but it is -# retained since deprecating it isn't worth the hassle -ResultBase = _NetlocResultMixinStr - -# Structured result objects for string data -class DefragResult(_DefragResultBase, _ResultMixinStr): - __slots__ = () - - def geturl(self): - if self.fragment: - return self.url + "#" + self.fragment - else: - return self.url - - -class SplitResult(_SplitResultBase, _NetlocResultMixinStr): - __slots__ = () - - def geturl(self): - return urlunsplit(self) - - -class ParseResult(_ParseResultBase, _NetlocResultMixinStr): - __slots__ = () - - def geturl(self): - return urlunparse(self) - - -# Structured result objects for bytes data -class DefragResultBytes(_DefragResultBase, _ResultMixinBytes): - __slots__ = () - - def geturl(self): - if self.fragment: - return self.url + b"#" + self.fragment - else: - return self.url - - -class SplitResultBytes(_SplitResultBase, _NetlocResultMixinBytes): - __slots__ = () - - def geturl(self): - return urlunsplit(self) - - -class ParseResultBytes(_ParseResultBase, _NetlocResultMixinBytes): - __slots__ = () - - def geturl(self): - return urlunparse(self) - - -# Set up the encode/decode result pairs -def _fix_result_transcoding(): - _result_pairs = ( - (DefragResult, DefragResultBytes), - (SplitResult, SplitResultBytes), - (ParseResult, ParseResultBytes), - ) - for _decoded, _encoded in _result_pairs: - _decoded._encoded_counterpart = _encoded - _encoded._decoded_counterpart = _decoded - - -_fix_result_transcoding() -del _fix_result_transcoding - - -def urlparse(url, scheme="", allow_fragments=True): - """Parse a URL into 6 components: - :///;?# - Return a 6-tuple: (scheme, netloc, path, params, query, fragment). - Note that we don't break the components up in smaller bits - (e.g. netloc is a single string) and we don't expand % escapes.""" - url, scheme, _coerce_result = _coerce_args(url, scheme) - splitresult = urlsplit(url, scheme, allow_fragments) - scheme, netloc, url, query, fragment = splitresult - if scheme in uses_params and ";" in url: - url, params = _splitparams(url) - else: - params = "" - result = ParseResult(scheme, netloc, url, params, query, fragment) - return _coerce_result(result) - - -def _splitparams(url): - if "/" in url: - i = url.find(";", url.rfind("/")) - if i < 0: - return url, "" - else: - i = url.find(";") - return url[:i], url[i + 1 :] - - -def _splitnetloc(url, start=0): - delim = len(url) # position of end of domain part of url, default is end - for c in "/?#": # look for delimiters; the order is NOT important - wdelim = url.find(c, start) # find first of this delim - if wdelim >= 0: # if found - delim = min(delim, wdelim) # use earliest delim position - return url[start:delim], url[delim:] # return (domain, rest) - - -def urlsplit(url, scheme="", allow_fragments=True): - """Parse a URL into 5 components: - :///?# - Return a 5-tuple: (scheme, netloc, path, query, fragment). - Note that we don't break the components up in smaller bits - (e.g. netloc is a single string) and we don't expand % escapes.""" - url, scheme, _coerce_result = _coerce_args(url, scheme) - allow_fragments = bool(allow_fragments) - key = url, scheme, allow_fragments, type(url), type(scheme) - cached = _parse_cache.get(key, None) - if cached: - return _coerce_result(cached) - if len(_parse_cache) >= MAX_CACHE_SIZE: # avoid runaway growth - clear_cache() - netloc = query = fragment = "" - i = url.find(":") - if i > 0: - if url[:i] == "http": # optimize the common case - scheme = url[:i].lower() - url = url[i + 1 :] - if url[:2] == "//": - netloc, url = _splitnetloc(url, 2) - if ("[" in netloc and "]" not in netloc) or ("]" in netloc and "[" not in netloc): - raise ValueError("Invalid IPv6 URL") - if allow_fragments and "#" in url: - url, fragment = url.split("#", 1) - if "?" in url: - url, query = url.split("?", 1) - v = SplitResult(scheme, netloc, url, query, fragment) - _parse_cache[key] = v - return _coerce_result(v) - for c in url[:i]: - if c not in scheme_chars: - break - else: - # make sure "url" is not actually a port number (in which case - # "scheme" is really part of the path) - rest = url[i + 1 :] - if not rest or any(c not in "0123456789" for c in rest): - # not a port number - scheme, url = url[:i].lower(), rest - - if url[:2] == "//": - netloc, url = _splitnetloc(url, 2) - if ("[" in netloc and "]" not in netloc) or ("]" in netloc and "[" not in netloc): - raise ValueError("Invalid IPv6 URL") - if allow_fragments and "#" in url: - url, fragment = url.split("#", 1) - if "?" in url: - url, query = url.split("?", 1) - v = SplitResult(scheme, netloc, url, query, fragment) - _parse_cache[key] = v - return _coerce_result(v) - - -def urlunparse(components): - """Put a parsed URL back together again. This may result in a - slightly different, but equivalent URL, if the URL that was parsed - originally had redundant delimiters, e.g. a ? with an empty query - (the draft states that these are equivalent).""" - scheme, netloc, url, params, query, fragment, _coerce_result = _coerce_args(*components) - if params: - url = "%s;%s" % (url, params) - return _coerce_result(urlunsplit((scheme, netloc, url, query, fragment))) - - -def urlunsplit(components): - """Combine the elements of a tuple as returned by urlsplit() into a - complete URL as a string. The data argument can be any five-item iterable. - This may result in a slightly different, but equivalent URL, if the URL that - was parsed originally had unnecessary delimiters (for example, a ? with an - empty query; the RFC states that these are equivalent).""" - scheme, netloc, url, query, fragment, _coerce_result = _coerce_args(*components) - if netloc or (scheme and scheme in uses_netloc and url[:2] != "//"): - if url and url[:1] != "/": - url = "/" + url - url = "//" + (netloc or "") + url - if scheme: - url = scheme + ":" + url - if query: - url = url + "?" + query - if fragment: - url = url + "#" + fragment - return _coerce_result(url) - - -def urljoin(base, url, allow_fragments=True): - """Join a base URL and a possibly relative URL to form an absolute - interpretation of the latter.""" - if not base: - return url - if not url: - return base - base, url, _coerce_result = _coerce_args(base, url) - bscheme, bnetloc, bpath, bparams, bquery, bfragment = urlparse(base, "", allow_fragments) - scheme, netloc, path, params, query, fragment = urlparse(url, bscheme, allow_fragments) - if scheme != bscheme or scheme not in uses_relative: - return _coerce_result(url) - if scheme in uses_netloc: - if netloc: - return _coerce_result(urlunparse((scheme, netloc, path, params, query, fragment))) - netloc = bnetloc - if path[:1] == "/": - return _coerce_result(urlunparse((scheme, netloc, path, params, query, fragment))) - if not path and not params: - path = bpath - params = bparams - if not query: - query = bquery - return _coerce_result(urlunparse((scheme, netloc, path, params, query, fragment))) - segments = bpath.split("/")[:-1] + path.split("/") - # XXX The stuff below is bogus in various ways... - if segments[-1] == ".": - segments[-1] = "" - while "." in segments: - segments.remove(".") - while 1: - i = 1 - n = len(segments) - 1 - while i < n: - if segments[i] == ".." and segments[i - 1] not in ("", ".."): - del segments[i - 1 : i + 1] - break - i = i + 1 - else: - break - if segments == ["", ".."]: - segments[-1] = "" - elif len(segments) >= 2 and segments[-1] == "..": - segments[-2:] = [""] - return _coerce_result( - urlunparse((scheme, netloc, "/".join(segments), params, query, fragment)) - ) - - -def urldefrag(url): - """Removes any existing fragment from URL. - - Returns a tuple of the defragmented URL and the fragment. If - the URL contained no fragments, the second element is the - empty string. - """ - url, _coerce_result = _coerce_args(url) - if "#" in url: - s, n, p, a, q, frag = urlparse(url) - defrag = urlunparse((s, n, p, a, q, "")) - else: - frag = "" - defrag = url - return _coerce_result(DefragResult(defrag, frag)) - - -_hexdig = "0123456789ABCDEFabcdef" -_hextobyte = {(a + b).encode(): bytes([int(a + b, 16)]) for a in _hexdig for b in _hexdig} - - -def unquote_to_bytes(string): - """unquote_to_bytes('abc%20def') -> b'abc def'.""" - # Note: strings are encoded as UTF-8. This is only an issue if it contains - # unescaped non-ASCII characters, which URIs should not. - if not string: - # Is it a string-like object? - string.split - return b"" - if isinstance(string, str): - string = string.encode("utf-8") - bits = string.split(b"%") - if len(bits) == 1: - return string - res = [bits[0]] - append = res.append - for item in bits[1:]: - try: - append(_hextobyte[item[:2]]) - append(item[2:]) - except KeyError: - append(b"%") - append(item) - return b"".join(res) - - -_asciire = re.compile(r"([\x00-\x7f]+)") - - -def unquote(string, encoding="utf-8", errors="replace"): - """Replace %xx escapes by their single-character equivalent. The optional - encoding and errors parameters specify how to decode percent-encoded - sequences into Unicode characters, as accepted by the bytes.decode() - method. - By default, percent-encoded sequences are decoded with UTF-8, and invalid - sequences are replaced by a placeholder character. - - unquote('abc%20def') -> 'abc def'. - """ - if "%" not in string: - string.split - return string - if encoding is None: - encoding = "utf-8" - if errors is None: - errors = "replace" - bits = _asciire.split(string) - res = [bits[0]] - append = res.append - for i in range(1, len(bits), 2): - append(unquote_to_bytes(bits[i]).decode(encoding, errors)) - append(bits[i + 1]) - return "".join(res) - - -def parse_qs( - qs, keep_blank_values=False, strict_parsing=False, encoding="utf-8", errors="replace" -): - """Parse a query given as a string argument. - - Arguments: - - qs: percent-encoded query string to be parsed - - keep_blank_values: flag indicating whether blank values in - percent-encoded queries should be treated as blank strings. - A true value indicates that blanks should be retained as - blank strings. The default false value indicates that - blank values are to be ignored and treated as if they were - not included. - - strict_parsing: flag indicating what to do with parsing errors. - If false (the default), errors are silently ignored. - If true, errors raise a ValueError exception. - - encoding and errors: specify how to decode percent-encoded sequences - into Unicode characters, as accepted by the bytes.decode() method. - """ - parsed_result = {} - pairs = parse_qsl(qs, keep_blank_values, strict_parsing, encoding=encoding, errors=errors) - for name, value in pairs: - if name in parsed_result: - parsed_result[name].append(value) - else: - parsed_result[name] = [value] - return parsed_result - - -def parse_qsl( - qs, keep_blank_values=False, strict_parsing=False, encoding="utf-8", errors="replace" -): - """Parse a query given as a string argument. - - Arguments: - - qs: percent-encoded query string to be parsed - - keep_blank_values: flag indicating whether blank values in - percent-encoded queries should be treated as blank strings. A - true value indicates that blanks should be retained as blank - strings. The default false value indicates that blank values - are to be ignored and treated as if they were not included. - - strict_parsing: flag indicating what to do with parsing errors. If - false (the default), errors are silently ignored. If true, - errors raise a ValueError exception. - - encoding and errors: specify how to decode percent-encoded sequences - into Unicode characters, as accepted by the bytes.decode() method. - - Returns a list, as G-d intended. - """ - qs, _coerce_result = _coerce_args(qs) - pairs = [s2 for s1 in qs.split("&") for s2 in s1.split(";")] - r = [] - for name_value in pairs: - if not name_value and not strict_parsing: - continue - nv = name_value.split("=", 1) - if len(nv) != 2: - if strict_parsing: - raise ValueError("bad query field: %r" % (name_value,)) - # Handle case of a control-name with no equal sign - if keep_blank_values: - nv.append("") - else: - continue - if len(nv[1]) or keep_blank_values: - name = nv[0].replace("+", " ") - name = unquote(name, encoding=encoding, errors=errors) - name = _coerce_result(name) - value = nv[1].replace("+", " ") - value = unquote(value, encoding=encoding, errors=errors) - value = _coerce_result(value) - r.append((name, value)) - return r - - -def unquote_plus(string, encoding="utf-8", errors="replace"): - """Like unquote(), but also replace plus signs by spaces, as required for - unquoting HTML form values. - - unquote_plus('%7e/abc+def') -> '~/abc def' - """ - string = string.replace("+", " ") - return unquote(string, encoding, errors) - - -_ALWAYS_SAFE = frozenset( - b"ABCDEFGHIJKLMNOPQRSTUVWXYZ" b"abcdefghijklmnopqrstuvwxyz" b"0123456789" b"_.-" -) -_ALWAYS_SAFE_BYTES = bytes(_ALWAYS_SAFE) -_safe_quoters = {} - - -class Quoter(collections.defaultdict): - """A mapping from bytes (in range(0,256)) to strings. - - String values are percent-encoded byte values, unless the key < 128, and - in the "safe" set (either the specified safe set, or default set). - """ - - # Keeps a cache internally, using defaultdict, for efficiency (lookups - # of cached keys don't call Python code at all). - def __init__(self, safe): - """safe: bytes object.""" - self.safe = _ALWAYS_SAFE.union(safe) - - def __repr__(self): - # Without this, will just display as a defaultdict - return "" % dict(self) - - def __missing__(self, b): - # Handle a cache miss. Store quoted string in cache and return. - res = chr(b) if b in self.safe else "%{:02X}".format(b) - self[b] = res - return res - - -def quote(string, safe="/", encoding=None, errors=None): - """quote('abc def') -> 'abc%20def' - - Each part of a URL, e.g. the path info, the query, etc., has a - different set of reserved characters that must be quoted. - - RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists - the following reserved characters. - - reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | - "$" | "," - - Each of these characters is reserved in some component of a URL, - but not necessarily in all of them. - - By default, the quote function is intended for quoting the path - section of a URL. Thus, it will not encode '/'. This character - is reserved, but in typical usage the quote function is being - called on a path where the existing slash characters are used as - reserved characters. - - string and safe may be either str or bytes objects. encoding must - not be specified if string is a str. - - The optional encoding and errors parameters specify how to deal with - non-ASCII characters, as accepted by the str.encode method. - By default, encoding='utf-8' (characters are encoded with UTF-8), and - errors='strict' (unsupported characters raise a UnicodeEncodeError). - """ - if isinstance(string, str): - if not string: - return string - if encoding is None: - encoding = "utf-8" - if errors is None: - errors = "strict" - string = string.encode(encoding, errors) - else: - if encoding is not None: - raise TypeError("quote() doesn't support 'encoding' for bytes") - if errors is not None: - raise TypeError("quote() doesn't support 'errors' for bytes") - return quote_from_bytes(string, safe) - - -def quote_plus(string, safe="", encoding=None, errors=None): - """Like quote(), but also replace ' ' with '+', as required for quoting - HTML form values. Plus signs in the original string are escaped unless - they are included in safe. It also does not have safe default to '/'. - """ - # Check if ' ' in string, where string may either be a str or bytes. If - # there are no spaces, the regular quote will produce the right answer. - if (isinstance(string, str) and " " not in string) or ( - isinstance(string, bytes) and b" " not in string - ): - return quote(string, safe, encoding, errors) - if isinstance(safe, str): - space = " " - else: - space = b" " - string = quote(string, safe + space, encoding, errors) - return string.replace(" ", "+") - - -def quote_from_bytes(bs, safe="/"): - """Like quote(), but accepts a bytes object rather than a str, and does - not perform string-to-bytes encoding. It always returns an ASCII string. - quote_from_bytes(b'abc def\x3f') -> 'abc%20def%3f' - """ - if not isinstance(bs, (bytes, bytearray)): - raise TypeError("quote_from_bytes() expected bytes") - if not bs: - return "" - if isinstance(safe, str): - # Normalize 'safe' by converting to bytes and removing non-ASCII chars - safe = safe.encode("ascii", "ignore") - else: - safe = bytes([c for c in safe if c < 128]) - if not bs.rstrip(_ALWAYS_SAFE_BYTES + safe): - return bs.decode() - try: - quoter = _safe_quoters[safe] - except KeyError: - _safe_quoters[safe] = quoter = Quoter(safe).__getitem__ - return "".join([quoter(char) for char in bs]) - - -def urlencode(query, doseq=False, safe="", encoding=None, errors=None): - """Encode a dict or sequence of two-element tuples into a URL query string. - - If any values in the query arg are sequences and doseq is true, each - sequence element is converted to a separate parameter. - - If the query arg is a sequence of two-element tuples, the order of the - parameters in the output will match the order of parameters in the - input. - - The components of a query arg may each be either a string or a bytes type. - When a component is a string, the safe, encoding and error parameters are - sent to the quote_plus function for encoding. - """ - - if hasattr(query, "items"): - query = query.items() - else: - # It's a bother at times that strings and string-like objects are - # sequences. - try: - # non-sequence items should not work with len() - # non-empty strings will fail this - if len(query) and not isinstance(query[0], tuple): - raise TypeError - # Zero-length sequences of all types will get here and succeed, - # but that's a minor nit. Since the original implementation - # allowed empty dicts that type of behavior probably should be - # preserved for consistency - except TypeError: - # ty, va, tb = sys.exc_info() - raise TypeError( - "not a valid non-string sequence " "or mapping object" - ) # .with_traceback(tb) - - l = [] - if not doseq: - for k, v in query: - if isinstance(k, bytes): - k = quote_plus(k, safe) - else: - k = quote_plus(str(k), safe, encoding, errors) - - if isinstance(v, bytes): - v = quote_plus(v, safe) - else: - v = quote_plus(str(v), safe, encoding, errors) - l.append(k + "=" + v) - else: - for k, v in query: - if isinstance(k, bytes): - k = quote_plus(k, safe) - else: - k = quote_plus(str(k), safe, encoding, errors) - - if isinstance(v, bytes): - v = quote_plus(v, safe) - l.append(k + "=" + v) - elif isinstance(v, str): - v = quote_plus(v, safe, encoding, errors) - l.append(k + "=" + v) - else: - try: - # Is this a sufficient test for sequence-ness? - x = len(v) - except TypeError: - # not a sequence - v = quote_plus(str(v), safe, encoding, errors) - l.append(k + "=" + v) - else: - # loop over the sequence - for elt in v: - if isinstance(elt, bytes): - elt = quote_plus(elt, safe) - else: - elt = quote_plus(str(elt), safe, encoding, errors) - l.append(k + "=" + elt) - return "&".join(l) - - -# Utilities to parse URLs (most of these return None for missing parts): -# unwrap('') --> 'type://host/path' -# splittype('type:opaquestring') --> 'type', 'opaquestring' -# splithost('//host[:port]/path') --> 'host[:port]', '/path' -# splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]' -# splitpasswd('user:passwd') -> 'user', 'passwd' -# splitport('host:port') --> 'host', 'port' -# splitquery('/path?query') --> '/path', 'query' -# splittag('/path#tag') --> '/path', 'tag' -# splitattr('/path;attr1=value1;attr2=value2;...') -> -# '/path', ['attr1=value1', 'attr2=value2', ...] -# splitvalue('attr=value') --> 'attr', 'value' -# urllib.parse.unquote('abc%20def') -> 'abc def' -# quote('abc def') -> 'abc%20def') - - -def to_bytes(url): - """to_bytes(u"URL") --> 'URL'.""" - # Most URL schemes require ASCII. If that changes, the conversion - # can be relaxed. - # XXX get rid of to_bytes() - if isinstance(url, str): - try: - url = url.encode("ASCII").decode() - except UnicodeError: - raise UnicodeError("URL " + repr(url) + " contains non-ASCII characters") - return url - - -def unwrap(url): - """unwrap('') --> 'type://host/path'.""" - url = str(url).strip() - if url[:1] == "<" and url[-1:] == ">": - url = url[1:-1].strip() - if url[:4] == "URL:": - url = url[4:].strip() - return url - - -_typeprog = None - - -def splittype(url): - """splittype('type:opaquestring') --> 'type', 'opaquestring'.""" - global _typeprog - if _typeprog is None: - import re - - _typeprog = re.compile("^([^/:]+):") - - match = _typeprog.match(url) - if match: - scheme = match.group(1) - return scheme.lower(), url[len(scheme) + 1 :] - return None, url - - -_hostprog = None - - -def splithost(url): - """splithost('//host[:port]/path') --> 'host[:port]', '/path'.""" - global _hostprog - if _hostprog is None: - import re - - _hostprog = re.compile("^//([^/?]*)(.*)$") - - match = _hostprog.match(url) - if match: - host_port = match.group(1) - path = match.group(2) - if path and not path.startswith("/"): - path = "/" + path - return host_port, path - return None, url - - -_userprog = None - - -def splituser(host): - """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" - global _userprog - if _userprog is None: - import re - - _userprog = re.compile("^(.*)@(.*)$") - - match = _userprog.match(host) - if match: - return match.group(1, 2) - return None, host - - -_passwdprog = None - - -def splitpasswd(user): - """splitpasswd('user:passwd') -> 'user', 'passwd'.""" - global _passwdprog - if _passwdprog is None: - import re - - _passwdprog = re.compile("^([^:]*):(.*)$", re.S) - - match = _passwdprog.match(user) - if match: - return match.group(1, 2) - return user, None - - -# splittag('/path#tag') --> '/path', 'tag' -_portprog = None - - -def splitport(host): - """splitport('host:port') --> 'host', 'port'.""" - global _portprog - if _portprog is None: - import re - - _portprog = re.compile("^(.*):([0-9]+)$") - - match = _portprog.match(host) - if match: - return match.group(1, 2) - return host, None - - -_nportprog = None - - -def splitnport(host, defport=-1): - """Split host and port, returning numeric port. - Return given default port if no ':' found; defaults to -1. - Return numerical port if a valid number are found after ':'. - Return None if ':' but not a valid number.""" - global _nportprog - if _nportprog is None: - import re - - _nportprog = re.compile("^(.*):(.*)$") - - match = _nportprog.match(host) - if match: - host, port = match.group(1, 2) - try: - if not port: - raise ValueError("no digits") - nport = int(port) - except ValueError: - nport = None - return host, nport - return host, defport - - -_queryprog = None - - -def splitquery(url): - """splitquery('/path?query') --> '/path', 'query'.""" - global _queryprog - if _queryprog is None: - import re - - _queryprog = re.compile("^(.*)\?([^?]*)$") - - match = _queryprog.match(url) - if match: - return match.group(1, 2) - return url, None - - -_tagprog = None - - -def splittag(url): - """splittag('/path#tag') --> '/path', 'tag'.""" - global _tagprog - if _tagprog is None: - import re - - _tagprog = re.compile("^(.*)#([^#]*)$") - - match = _tagprog.match(url) - if match: - return match.group(1, 2) - return url, None - - -def splitattr(url): - """splitattr('/path;attr1=value1;attr2=value2;...') -> - '/path', ['attr1=value1', 'attr2=value2', ...].""" - words = url.split(";") - return words[0], words[1:] - - -_valueprog = None - - -def splitvalue(attr): - """splitvalue('attr=value') --> 'attr', 'value'.""" - global _valueprog - if _valueprog is None: - import re - - _valueprog = re.compile("^([^=]*)=(.*)$") - - match = _valueprog.match(attr) - if match: - return match.group(1, 2) - return attr, None diff --git a/modules/ulinksdk/warnings.py b/modules/ulinksdk/warnings.py deleted file mode 100644 index 1cb31b5389..0000000000 --- a/modules/ulinksdk/warnings.py +++ /dev/null @@ -1,2 +0,0 @@ -def warn(msg, cat=None, stacklevel=1): - print("%s: %s" % ("Warning" if cat is None else cat.__name__, msg)) diff --git a/modules/umqtt.robust/umqtt/robust2.py b/modules/umqtt.robust/umqtt/robust2.py new file mode 100644 index 0000000000..451df455be --- /dev/null +++ b/modules/umqtt.robust/umqtt/robust2.py @@ -0,0 +1,121 @@ +from utime import ticks_ms, ticks_diff +from . import simple2 +class MQTTClient(simple2.MQTTClient): + DEBUG=False;KEEP_QOS0=True;NO_QUEUE_DUPS=True;MSG_QUEUE_MAX=5;CONFIRM_QUEUE_MAX=10;RESUBSCRIBE=True + def __init__(A,*B,**C):super().__init__(*B,**C);A.subs=[];A.msg_to_send=[];A.sub_to_send=[];A.msg_to_confirm={};A.sub_to_confirm={};A.conn_issue=None + def is_keepalive(A): + B=ticks_diff(ticks_ms(),A.last_cpacket)//1000 + if 0=A.MSG_QUEUE_MAX: + E=min(map(lambda x:x[0]if x else 65535,A.msg_to_confirm.values()),default=0) + if 0A.CONFIRM_QUEUE_MAX:A.msg_to_confirm.pop(0) + return F + except (OSError,simple2.MQTTException)as G: + A.conn_issue=G,2 + if A.NO_QUEUE_DUPS: + if C in A.msg_to_send:return + if A.KEEP_QOS0 and B==0:A.add_msg_to_send(C) + elif B==1:A.add_msg_to_send(C) + def subscribe(A,topic,qos=0,resubscribe=True): + C=topic;B=C,qos + if A.RESUBSCRIBE and resubscribe: + if C not in dict(A.subs):A.subs.append(B) + A.sub_to_send[:]=[B for B in A.sub_to_send if C!=B[0]] + try: + D=super().subscribe(C,qos);A.sub_to_confirm.setdefault(B,[]).append(D) + if len(A.sub_to_confirm[B])>A.CONFIRM_QUEUE_MAX:A.sub_to_confirm.pop(0) + return D + except (OSError,simple2.MQTTException)as E: + A.conn_issue=E,3 + if A.NO_QUEUE_DUPS: + if B in A.sub_to_send:return + A.sub_to_send.append(B) + def send_queue(A): + D=[] + for B in A.msg_to_send: + E,I,J,C=B + try: + F=super().publish(E,I,J,C,False) + if C==1:A.msg_to_confirm.setdefault(B,[]).append(F) + D.append(B) + except (OSError,simple2.MQTTException)as G:A.conn_issue=G,5;return False + A.msg_to_send[:]=[B for B in A.msg_to_send if B not in D];del D;H=[] + for B in A.sub_to_send: + E,C=B + try:F=super().subscribe(E,C);A.sub_to_confirm.setdefault(B,[]).append(F);H.append(B) + except (OSError,simple2.MQTTException)as G:A.conn_issue=G,5;return False + A.sub_to_send[:]=[B for B in A.sub_to_send if B not in H];return True + def is_conn_issue(A): + A.is_keepalive() + if A.conn_issue:A.log() + return bool(A.conn_issue) + def wait_msg(A): + A.is_keepalive() + try:return super().wait_msg() + except (OSError,simple2.MQTTException)as B:A.conn_issue=B,8 + def check_msg(A): + A.is_keepalive() + try:return super().check_msg() + except (OSError,simple2.MQTTException)as B:A.conn_issue=B,10 diff --git a/modules/umqtt.simple/umqtt/simple2.py b/modules/umqtt.simple/umqtt/simple2.py new file mode 100644 index 0000000000..8af073f7f0 --- /dev/null +++ b/modules/umqtt.simple/umqtt/simple2.py @@ -0,0 +1,132 @@ +import usocket as socket +import uselect +from utime import ticks_add,ticks_ms,ticks_diff +class MQTTException(Exception):0 +def pid_gen(pid=0): + A=pid + while True:A=A+1 if A<65535 else 1;yield A +class MQTTClient: + def __init__(A,client_id,server,port=0,user=None,password=None,keepalive=0,ssl=False,ssl_params=None,socket_timeout=5,message_timeout=10): + C=ssl_params;B=port + if B==0:B=8883 if ssl else 1883 + A.client_id=client_id;A.sock=None;A.poller_r=None;A.poller_w=None;A.server=server;A.port=B;A.ssl=ssl;A.ssl_params=C if C else{};A.newpid=pid_gen() + if not getattr(A,'cb',None):A.cb=None + if not getattr(A,'cbstat',None):A.cbstat=lambda p,s:None + A.user=user;A.pswd=password;A.keepalive=keepalive;A.lw_topic=None;A.lw_msg=None;A.lw_qos=0;A.lw_retain=False;A.rcv_pids={};A.last_ping=ticks_ms();A.last_cpacket=ticks_ms();A.socket_timeout=socket_timeout;A.message_timeout=message_timeout + def _read(A,n): + try: + B=b'' + for C in range(n):A._sock_timeout(A.poller_r,A.socket_timeout);B+=A.sock.read(1) + except AttributeError:raise MQTTException(8) + if B==b'':raise MQTTException(1) + if len(B)!=n:raise MQTTException(2) + return B + def _write(A,bytes_wr,length=-1): + D=bytes_wr;B=length + try:A._sock_timeout(A.poller_w,A.socket_timeout);C=A.sock.write(D,B) + except AttributeError:raise MQTTException(8) + if B<0: + if C!=len(D):raise MQTTException(3) + elif C!=B:raise MQTTException(3) + return C + def _send_str(A,s):assert len(s)<65536;A._write(len(s).to_bytes(2,'big'));A._write(s) + def _recv_len(D): + A=0;B=0 + while 1: + C=D._read(1)[0];A|=(C&127)<127:buf[B]=A&127|128;A>>=7;B+=1 + buf[B]=A;return B+1 + def _sock_timeout(B,poller,socket_timeout): + A=socket_timeout + if B.sock: + C=poller.poll(-1 if A is None else int(A*1000)) + if not C:raise MQTTException(30) + else:raise MQTTException(28) + def set_callback(A,f):A.cb=f + def set_callback_status(A,f):A.cbstat=f + def set_last_will(A,topic,msg,retain=False,qos=0):B=topic;assert 0<=qos<=2;assert B;A.lw_topic=B;A.lw_msg=msg;A.lw_qos=qos;A.lw_retain=retain + def connect(A,clean_session=True): + E=clean_session;A.sock=socket.socket();G=socket.getaddrinfo(A.server,A.port)[0][-1];A.sock.connect(G) + if A.ssl:import ussl;A.sock=ussl.wrap_socket(A.sock,**A.ssl_params) + A.poller_r=uselect.poll();A.poller_r.register(A.sock,uselect.POLLIN);A.poller_w=uselect.poll();A.poller_w.register(A.sock,uselect.POLLOUT);F=bytearray(b'\x10\x00\x00\x00\x00\x00');B=bytearray(b'\x00\x04MQTT\x04\x00\x00\x00');D=10+2+len(A.client_id);B[7]=bool(E)<<1 + if bool(E):A.rcv_pids.clear() + if A.user is not None: + D+=2+len(A.user);B[7]|=1<<7 + if A.pswd is not None:D+=2+len(A.pswd);B[7]|=1<<6 + if A.keepalive:assert A.keepalive<65536;B[8]|=A.keepalive>>8;B[9]|=A.keepalive&255 + if A.lw_topic:D+=2+len(A.lw_topic)+2+len(A.lw_msg);B[7]|=4|(A.lw_qos&1)<<3|(A.lw_qos&2)<<3;B[7]|=A.lw_retain<<5 + H=A._varlen_encode(D,F,1);A._write(F,H);A._write(B);A._send_str(A.client_id) + if A.lw_topic:A._send_str(A.lw_topic);A._send_str(A.lw_msg) + if A.user is not None: + A._send_str(A.user) + if A.pswd is not None:A._send_str(A.pswd) + C=A._read(4) + if not(C[0]==32 and C[1]==2):raise MQTTException(29) + if C[3]!=0: + if 1<=C[3]<=5:raise MQTTException(20+C[3]) + else:raise MQTTException(20,C[3]) + A.last_cpacket=ticks_ms();return C[2]&1 + def disconnect(A):A._write(b'\xe0\x00');A.poller_r.unregister(A.sock);A.poller_w.unregister(A.sock);A.poller_r=None;A.poller_w=None;A.sock.close();A.sock=None + def ping(A):A._write(b'\xc0\x00');A.last_ping=ticks_ms() + def publish(A,topic,msg,retain=False,qos=0,dup=False): + E=topic;B=qos;assert B in(0,1);C=bytearray(b'0\x00\x00\x00\x00');C[0]|=B<<1|retain|int(dup)<<3;F=2+len(E)+len(msg) + if B>0:F+=2 + G=A._varlen_encode(F,C,1);A._write(C,G);A._send_str(E) + if B>0:D=next(A.newpid);A._write(D.to_bytes(2,'big')) + A._write(msg) + if B>0:A.rcv_pids[D]=ticks_add(ticks_ms(),A.message_timeout*1000);return D + + def subscribe(A,topic,qos=0):E=topic;assert qos in(0,1);assert A.cb is not None,'Subscribe callback is not set';B=bytearray(b'\x82\x00\x00\x00\x00\x00\x00');C=next(A.newpid);F=2+2+len(E)+1;D=A._varlen_encode(F,B,1);B[D:D+2]=C.to_bytes(2,'big');A._write(B,D+2);A._send_str(E);A._write(qos.to_bytes(1,'little'));A.rcv_pids[C]=ticks_add(ticks_ms(),A.message_timeout*1000);return C + def unsubscribe(A,topic,qos=0):E=topic;assert qos in(0,1);assert A.cb is not None,'Subscribe callback is not set';B=bytearray(b'\xA2\x00\x00\x00\x00\x00\x00');C=next(A.newpid);F=2+2+len(E);D=A._varlen_encode(F,B,1);B[D:D+2]=C.to_bytes(2,'big');A._write(B,D+2);A._send_str(E); A.rcv_pids[C]=ticks_add(ticks_ms(),A.message_timeout*1000);return C + + def _message_timeout(A): + C=ticks_ms() + for (B,D) in A.rcv_pids.items(): + if ticks_diff(D,C)<=0:A.rcv_pids.pop(B);A.cbstat(B,0) + def check_msg(A): + if A.sock: + if not A.poller_r.poll(-1 if A.socket_timeout is None else 1):A._message_timeout();return None + try: + G=A._read(1) + if not G:A._message_timeout();return None + except OSError as H: + if H.args[0]==110:A._message_timeout();return None + else:raise H + else:raise MQTTException(28) + if G==b'\xd0': + if A._read(1)[0]!=0:MQTTException(-1) + A.last_cpacket=ticks_ms();return + B=G[0] + if B==64: + D=A._read(1) + if D!=b'\x02':raise MQTTException(-1) + F=int.from_bytes(A._read(2),'big') + if F in A.rcv_pids:A.last_cpacket=ticks_ms();A.rcv_pids.pop(F);A.cbstat(F,1) + else:A.cbstat(F,2) + if B==144: + C=A._read(4) + if C[0]!=3:raise MQTTException(40,C) + #if C[3]==128:raise MQTTException(44) + #if C[3]not in(0,1,2):raise MQTTException(40,C) + E=C[2]|C[1]<<8 + if E in A.rcv_pids:A.last_cpacket=ticks_ms();A.rcv_pids.pop(E);A.cbstat(E,1) + else:raise MQTTException(5) + A._message_timeout() + if B&240!=48:return B + D=A._recv_len();I=int.from_bytes(A._read(2),'big');J=A._read(I);D-=I+2 + if B&6:E=int.from_bytes(A._read(2),'big');D-=2 + K=A._read(D)if D else b'';L=B&1;M=B&8;A.cb(J,K,bool(L),bool(M));A.last_cpacket=ticks_ms() + if B&6==2:A._write(b'@\x02');A._write(E.to_bytes(2,'big')) + elif B&6==4:raise NotImplementedError() + elif B&6==6:raise MQTTException(-1) + def wait_msg(A):B=A.socket_timeout;A.socket_timeout=None;C=A.check_msg();A.socket_timeout=B;return C + def wait_msg_timeout(A): + B=A.socket_timeout + A.socket_timeout= 100 + C=A.check_msg() + A.socket_timeout=B + return C diff --git a/modules/utility/component.mk b/modules/utility/component.mk new file mode 100644 index 0000000000..adc961db80 --- /dev/null +++ b/modules/utility/component.mk @@ -0,0 +1,17 @@ +# +# Component Makefile +# +COMPONENT_DIR = utility +COMPONENT_SUBMODULES += $(COMPONENT_DIR) + +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := src + +INC_HAAS += $(addprefix $(MODULES_DIR)/$(COMPONENT_DIR)/, $(COMPONENT_ADD_INCLUDEDIRS)) +SRC_HAAS += $(addprefix modules/$(COMPONENT_DIR)/$(COMPONENT_SRCDIRS)/,\ + modutility.c \ + crc16_ibm.c \ + crc16_modbus.c \ +) + +CFLAGS += -DMICROPY_PY_UTILITY=1 \ No newline at end of file diff --git a/modules/utility/include/crc16.h b/modules/utility/include/crc16.h new file mode 100644 index 0000000000..a7f6296906 --- /dev/null +++ b/modules/utility/include/crc16.h @@ -0,0 +1,29 @@ +/* + * + * This file is licensed under the MIT License as stated below + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +uint16_t crc_ibm(uint8_t const *buffer, int32_t len); +uint16_t crc_modbus(uint8_t const *buffer, int32_t len); \ No newline at end of file diff --git a/modules/utility/src/crc16_ibm.c b/modules/utility/src/crc16_ibm.c new file mode 100644 index 0000000000..13fc493966 --- /dev/null +++ b/modules/utility/src/crc16_ibm.c @@ -0,0 +1,41 @@ +/* + * CRC lookup table for bytes, generating polynomial is 0x8005 + * input: reflexed (LSB first) + * output: reflexed also... + */ + +#include + +const static uint16_t crc_ibm_table[256] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, + 0xc481, 0x0440, 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, + 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, + 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, + 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, + 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, + 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, + 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, + 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01, 0x6ac0, + 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, + 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, + 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, + 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, + 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, + 0x4100, 0x81c1, 0x8081, 0x4040, +}; + +uint16_t crc_ibm(uint8_t const *buffer, int32_t len) +{ + uint16_t crc = 0x0000; + uint8_t lut; + + while (len--) { + lut = (crc ^ (*buffer++)) & 0xFF; + crc = (crc >> 8) ^ crc_ibm_table[lut]; + } + return crc; +} diff --git a/modules/utility/src/crc16_modbus.c b/modules/utility/src/crc16_modbus.c new file mode 100644 index 0000000000..d9300c86fa --- /dev/null +++ b/modules/utility/src/crc16_modbus.c @@ -0,0 +1,20 @@ +#include "crc16.h" + +const static uint16_t crc_modbus_table[256] = { + 0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, + 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400 +}; + +uint16_t crc_modbus(uint8_t const *buffer, int32_t len) +{ + int32_t i = 0, ch = 0; + uint16_t crc = 0xFFFF; + for (i = 0; i < len - 2; i++) { + ch = buffer[i]; + crc = (uint16_t)(crc_modbus_table[(ch ^ crc) & 0x0F] ^ (crc >> 4)); + crc = (uint16_t)(crc_modbus_table[((ch >> 4) ^ crc) & 0x0F] ^ (crc >> 4)); + } + + crc = (uint16_t)((crc & 0xFF) << 8 | (crc >> 8)); + return crc; +} \ No newline at end of file diff --git a/modules/utility/src/modutility.c b/modules/utility/src/modutility.c new file mode 100644 index 0000000000..5c6904df05 --- /dev/null +++ b/modules/utility/src/modutility.c @@ -0,0 +1,59 @@ +#include "crc16.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/stackctrl.h" + +#if MICROPY_PY_UTILITY + +STATIC mp_obj_t mod_utility_crc16Ibm(mp_obj_t data_in) +{ + const char *data = NULL; + int32_t size = 0; + + if (mp_obj_is_type(data_in, &mp_type_bytearray)) { + mp_buffer_info_t bufinfo = { 0 }; + mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_WRITE); + data = (const char *)bufinfo.buf; + size = bufinfo.len; + } else if (mp_obj_is_type(data_in, &mp_type_bytes)) { + data = mp_obj_str_get_str(data_in); + size = strlen(data); + } else { + mp_raise_TypeError(MP_ERROR_TEXT("data should be bytearray or bytes")); + } + + return MP_OBJ_NEW_SMALL_INT(crc_ibm((const uint8_t *)data, size)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_utility_crc16Ibm_obj, mod_utility_crc16Ibm); + +STATIC mp_obj_t mod_utility_crc16Modbus(mp_obj_t data_in) +{ + if (!mp_obj_is_type(data_in, &mp_type_bytearray)) { + mp_raise_TypeError(MP_ERROR_TEXT("data should be bytearray")); + } + + mp_buffer_info_t bufinfo = { 0 }; + mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_WRITE); + const uint8_t *data = (const uint8_t *)bufinfo.buf; + int32_t size = bufinfo.len; + + return MP_OBJ_NEW_SMALL_INT(crc_modbus(data, size)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_utility_crc16Modbus_obj, mod_utility_crc16Modbus); + +STATIC const mp_rom_map_elem_t mp_module_utility_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utility) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_crc16Ibm), MP_ROM_PTR(&mod_utility_crc16Ibm_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_crc16Modbus), MP_ROM_PTR(&mod_utility_crc16Modbus_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_utility_globals, mp_module_utility_globals_table); + +const mp_obj_module_t mp_module_utility = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_utility_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_utility, mp_module_utility, MICROPY_PY_UTILITY); + +#endif // MICROPY_PY_UTILITY \ No newline at end of file diff --git a/modules/w5500/w5500.py b/modules/w5500/w5500.py new file mode 100644 index 0000000000..5c9d524478 --- /dev/null +++ b/modules/w5500/w5500.py @@ -0,0 +1,10 @@ +import network +from machine import Pin +from machine import SPI + +pinRst = Pin("F4", Pin.OUT) +pinCS = Pin("B12", Pin.OUT) +spi = SPI(2, 80000) + +nic = network.WIZNET5K(spi, pinRst, pinCS) +nic.ifconfig() \ No newline at end of file diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 8c716e958d..9c1f0875f4 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -38,6 +38,8 @@ #endif #endif +#define MICROPY_QSTR_BYTES_IN_HASH (1) + #define MICROPY_EMIT_X64 (1) #define MICROPY_EMIT_X86 (1) #define MICROPY_EMIT_THUMB (1) diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt index 8f5072cd87..29409adc74 100644 --- a/ports/esp32/CMakeLists.txt +++ b/ports/esp32/CMakeLists.txt @@ -5,26 +5,6 @@ cmake_minimum_required(VERSION 3.12) # Set the location of this port's directory. set(MICROPY_PORT_DIR ${CMAKE_SOURCE_DIR}) -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../engine ABSOLUTE) -endif() - -# Set the location of AOS components directory. -if(NOT AOS_COMPONENTS_DIR) - get_filename_component(AOS_COMPONENTS_DIR ${MICROPY_PORT_DIR}/../../../ ABSOLUTE) -endif() - -# Set the location of AOS engine directory. -if(NOT HAAS_ENGINE_DIR) - get_filename_component(HAAS_ENGINE_DIR ${MICROPY_PORT_DIR}/../../ ABSOLUTE) -endif() - -# Set the location of AOS hardware directory. -if(NOT AOS_HARDWARE_DIR) - get_filename_component(AOS_HARDWARE_DIR ${MICROPY_PORT_DIR}/../../../../hardware ABSOLUTE) -endif() - # Set the board if it's not already set. if(NOT MICROPY_BOARD) set(MICROPY_BOARD GENERIC) @@ -44,9 +24,6 @@ set(SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig) # Include board config; this is expected to set SDKCONFIG_DEFAULTS (among other options). include(${MICROPY_BOARD_DIR}/mpconfigboard.cmake) -# Include build options for port extend modules. -include(${HAAS_ENGINE_DIR}/modules/config.cmake) - # Concatenate all sdkconfig files into a combined one for the IDF to use. file(WRITE ${CMAKE_BINARY_DIR}/sdkconfig.combined.in "") foreach(SDKCONFIG_DEFAULT ${SDKCONFIG_DEFAULTS}) @@ -58,21 +35,4 @@ set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) # Include main IDF cmake file and define the project. include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if(MICROPY_PY_LVGL) - # Include LVGL component, ignore KCONFIG - idf_build_set_property(LV_MICROPYTHON 1) - idf_build_set_property(LV_ESPIDF_ENABLE 0) - set(LVGL_DIR ${MICROPY_DIR}/lib/lv_bindings/lvgl) - set(LV_CONFIG_DIR ${MICROPY_DIR}/../modules/display/esp32) - idf_build_component(${LVGL_DIR}) - idf_build_component(${AOS_COMPONENTS_DIR}/drivers/external_device/m5driver/axp192) - idf_build_component(${AOS_COMPONENTS_DIR}/drivers/external_device/m5driver/i2c_manager) - idf_build_component(${AOS_COMPONENTS_DIR}/drivers/external_device/m5driver/lvgl_esp32_drivers) - idf_build_set_property(COMPILE_DEFINITIONS "-DLV_KCONFIG_IGNORE" APPEND) - separate_arguments(LV_CFLAGS_ENV UNIX_COMMAND $ENV{LV_CFLAGS}) - list(APPEND LV_CFLAGS ${LV_CFLAGS_ENV}) - idf_build_set_property(COMPILE_DEFINITIONS "${LV_CFLAGS}" APPEND) -endif() - project(micropython) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 27f5f6485f..4820b0dfdd 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -3,20 +3,21 @@ # This is a simple, convenience wrapper around idf.py (which uses cmake). # Select the board to build for, defaulting to GENERIC. + BOARD ?= GENERIC # If the build directory is not given, make it reflect the board name. BUILD ?= build-$(BOARD) # Device serial settings. -PORT ?= /dev/ttyUSB0 +PORT ?= /dev/tty.usbserial-0001 BAUD ?= 460800 PYTHON ?= python3 GIT_SUBMODULES = lib/berkeley-db-1.xx -.PHONY: all clean deploy erase submodules menuconfig FORCE +.PHONY: all clean deploy erase submodules FORCE CMAKE_ARGS = @@ -26,11 +27,6 @@ endif IDFPY_FLAGS += -D MICROPY_BOARD=$(BOARD) -B $(BUILD) $(CMAKE_ARGS) -mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) -current_dir := $(abspath $(patsubst %/,%,$(dir $(mkfile_path)))) - -FROZEN_MANIFEST ?= ${current_dir}/boards/manifest_release.py - ifdef FROZEN_MANIFEST IDFPY_FLAGS += -D MICROPY_FROZEN_MANIFEST=$(FROZEN_MANIFEST) endif @@ -57,6 +53,3 @@ erase: submodules: git submodule update --init $(addprefix ../../,$(GIT_SUBMODULES)) - -menuconfig: - idf.py $(IDFPY_FLAGS) menuconfig diff --git a/ports/esp32/README.md b/ports/esp32/README.md index c7d40070d1..42cd588171 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -75,6 +75,12 @@ $ source export.sh # (or export.bat on Windows) The `install.sh` step only needs to be done once. You will need to source `export.sh` for every new session. +**Note:** If you are building MicroPython for the ESP32-S2, ESP32-C3 or ESP32-S3, +please ensure you are using the following required IDF versions: +- ESP32-S3 currently requires latest `master`, but eventually `v4.4` or later when + it's available. +- ESP32-S2 and ESP32-C3 require `v4.3.1` or later. + Building the firmware --------------------- diff --git a/ports/esp32/board_config.h b/ports/esp32/board_config.h deleted file mode 100644 index 1d703801db..0000000000 --- a/ports/esp32/board_config.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2015-2020 Alibaba Group Holding Limited - */ - -#ifndef __AMP_MACHINE_CONFIG_H__ -#define __AMP_MACHINE_CONFIG_H__ - -/* python engine root dir define */ -#define MP_PY_PATH "main.py" -#define MP_FS_IDF_DIR "/" - -#define MP_FS_ROOT_DIR "/data" -#define MP_FS_EXT_ROOT_DIR "/sdcard" - -#define AMP_FS_ROOT_DIR MP_FS_ROOT_DIR "/pyamp" -#define AMP_FS_EXT_ROOT_DIR MP_FS_EXT_ROOT_DIR "/pyamp" - -#define AMP_PY_ENTRY_HAAS (AMP_FS_ROOT_DIR "/" MP_PY_PATH) -#define AMP_PY_ENTRY_ROOT "/main.py" - -#define MP_REPL_UART_PORT 0 -#define MP_REPL_UART_BAUDRATE 115200UL - -#define MP_RECOVERY_UART_PORT 0 -#define MP_RECOVERY_UART_PORT_BAUDRATE 115200UL - -#endif // __AMP_MACHINE_CONFIG_H__ diff --git a/ports/esp32/boards/ESP32_S2_WROVER/board.json b/ports/esp32/boards/ESP32_S2_WROVER/board.json new file mode 100644 index 0000000000..7ebc84415c --- /dev/null +++ b/ports/esp32/boards/ESP32_S2_WROVER/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy_s2.md" + ], + "docs": "", + "features": [ + "BLE", + "SPIRAM", + "WiFi" + ], + "images": [ + "ESP32-S2-WROVER_L_0.jpg" + ], + "mcu": "esp32s2", + "product": "ESP32-S2 WROVER", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.cmake b/ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.cmake new file mode 100644 index 0000000000..806312e5ac --- /dev/null +++ b/ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.cmake @@ -0,0 +1,12 @@ +set(IDF_TARGET esp32s2) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.spiram_sx + boards/sdkconfig.usb + boards/ESP32_S2_WROVER/sdkconfig.board +) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) +endif() diff --git a/ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.h b/ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.h new file mode 100644 index 0000000000..a96262c607 --- /dev/null +++ b/ports/esp32/boards/ESP32_S2_WROVER/mpconfigboard.h @@ -0,0 +1,8 @@ +#define MICROPY_HW_BOARD_NAME "ESP32-S2-WROVER" +#define MICROPY_HW_MCU_NAME "ESP32-S2" + +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_HW_ENABLE_SDCARD (0) + +#define MICROPY_HW_I2C0_SCL (7) +#define MICROPY_HW_I2C0_SDA (6) diff --git a/ports/esp32/boards/ESP32_S2_WROVER/sdkconfig.board b/ports/esp32/boards/ESP32_S2_WROVER/sdkconfig.board new file mode 100644 index 0000000000..9373a52232 --- /dev/null +++ b/ports/esp32/boards/ESP32_S2_WROVER/sdkconfig.board @@ -0,0 +1,11 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_USB_AND_UART=y + +# LWIP +CONFIG_LWIP_LOCAL_HOSTNAME="ESP32-S2-WROVER" +# end of LWIP diff --git a/ports/esp32/boards/GENERIC/board.json b/ports/esp32/boards/GENERIC/board.json new file mode 100644 index 0000000000..a52d58007d --- /dev/null +++ b/ports/esp32/boards/GENERIC/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "id": "esp32", + "images": [ + "esp32_devkitc.jpg" + ], + "mcu": "esp32", + "product": "ESP32", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "variants": { + "idf3": "Compiled with IDF 3.x" + }, + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC/board.md b/ports/esp32/boards/GENERIC/board.md new file mode 100644 index 0000000000..576ea80450 --- /dev/null +++ b/ports/esp32/boards/GENERIC/board.md @@ -0,0 +1,3 @@ +The following files are daily firmware for ESP32-based boards without external SPIRAM. + +This firmware is compiled using ESP-IDF v4.x. Some older releases are also provided that are compiled with ESP-IDF v3.x. diff --git a/ports/esp32/boards/GENERIC/manifest copy.py b/ports/esp32/boards/GENERIC/manifest copy.py new file mode 100644 index 0000000000..85c49f0f89 --- /dev/null +++ b/ports/esp32/boards/GENERIC/manifest copy.py @@ -0,0 +1,32 @@ +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +freeze("$(MPY_DIR)/drivers/dht", "dht.py") +# freeze("$(MPY_DIR)/drivers/display", ("lcd160cr.py", "lcd160cr_test.py")) +freeze("$(MPY_DIR)/drivers/onewire", "onewire.py") +freeze("$(MPY_DIR)/ports/stm32/modules", "neopixel.py") + +# upip +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) + +# boardparser +freeze("$(MPY_DIR)/modules/boardparser", "boardparser.py") + +# driver +include("$(MPY_DIR)/modules/driver/manifest.py") + +# modbus +include("$(MPY_DIR)/modules/modbus/manifest.py") + +# ulinksdk +include("$(MPY_DIR)/modules/ulinksdk/manifest.py") + +# http +freeze("$(MPY_DIR)/modules/http", "http.py") + +# ukv +freeze("$(MPY_DIR)/modules/ukv", ("kv.py", "systemAdaptor.py")) + +# oss +include("$(MPY_DIR)/modules/oss/manifest.py") + +# ukv +freeze("$(MPY_DIR)/modules/ukv", ("kv.py", "systemAdaptor.py")) diff --git a/ports/esp32/boards/GENERIC/manifest.py b/ports/esp32/boards/GENERIC/manifest.py new file mode 100644 index 0000000000..a76de53301 --- /dev/null +++ b/ports/esp32/boards/GENERIC/manifest.py @@ -0,0 +1,34 @@ +freeze("$(PORT_DIR)/modules") +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) +freeze("$(MPY_DIR)/ports/esp8266/modules", "ntptime.py") +freeze("$(MPY_DIR)/drivers/dht", "dht.py") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +include("$(MPY_DIR)/extmod/webrepl/manifest.py") +include("$(MPY_DIR)/drivers/neopixel/manifest.py") + +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +freeze("$(MPY_DIR)/drivers/onewire", "onewire.py") + +# upip +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) + +# boardparser +freeze("$(MPY_DIR)/modules/boardparser", "boardparser.py") + +# driver +include("$(MPY_DIR)/modules/driver/manifest.py") + +# modbus +include("$(MPY_DIR)/modules/modbus/manifest.py") + +# ulinksdk +include("$(MPY_DIR)/modules/ulinksdk/manifest.py") + +# http +freeze("$(MPY_DIR)/modules/http", "http.py") + +# ukv +freeze("$(MPY_DIR)/modules/ukv", "kv.py") +# ukv +freeze("$(MPY_DIR)/modules/adaptor/esp32/nodemcu32s", "systemAdaptor.py") diff --git a/ports/esp32/boards/GENERIC/manifest_release.py b/ports/esp32/boards/GENERIC/manifest_release.py new file mode 100644 index 0000000000..bf2d00ce72 --- /dev/null +++ b/ports/esp32/boards/GENERIC/manifest_release.py @@ -0,0 +1,9 @@ +include("manifest.py") + +freeze("$(MPY_DIR)/modules/urequests", "urequests.py") + +# freeze("$(MPY_LIB_DIR)/micropython/upysh", "upysh.py") +freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple.py") +freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust.py") +freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple2.py") +freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust2.py") diff --git a/ports/esp32/boards/GENERIC/mpconfigboard.cmake b/ports/esp32/boards/GENERIC/mpconfigboard.cmake index aebcf30497..c28171d0dc 100644 --- a/ports/esp32/boards/GENERIC/mpconfigboard.cmake +++ b/ports/esp32/boards/GENERIC/mpconfigboard.cmake @@ -1,11 +1,7 @@ -set(IDF_TARGET esp32) - set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.ble - boards/GENERIC/sdkconfig.board ) - if(NOT MICROPY_FROZEN_MANIFEST) - set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/GENERIC/manifest_release.py) endif() diff --git a/ports/esp32/boards/GENERIC/mpconfigboard.h b/ports/esp32/boards/GENERIC/mpconfigboard.h index fd46fabc1d..644807f78e 100644 --- a/ports/esp32/boards/GENERIC/mpconfigboard.h +++ b/ports/esp32/boards/GENERIC/mpconfigboard.h @@ -1,3 +1,2 @@ #define MICROPY_HW_BOARD_NAME "ESP32 module" #define MICROPY_HW_MCU_NAME "ESP32" -#define MICROPY_SW_VENDOR_NAME "HaaSPython" diff --git a/ports/esp32/boards/GENERIC/sdkconfig.board b/ports/esp32/boards/GENERIC/sdkconfig.board deleted file mode 100644 index 62afe64bf4..0000000000 --- a/ports/esp32/boards/GENERIC/sdkconfig.board +++ /dev/null @@ -1,23 +0,0 @@ - -CONFIG_MBEDTLS_PSK_MODES=y -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n -CONFIG_PARTITION_TABLE_OFFSET=0x15000 -CONFIG_BOOTLOADER_WDT_TIME_MS=120000 -CONFIG_FATFS_LFN_HEAP=y -CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY=y -CONFIG_FREERTOS_UNICORE=y -CONFIG_HEAP_POISONING_LIGHT=y -CONFIG_HEAP_TRACING_OFF=y -CONFIG_HEAP_TASK_TRACKING=y -CONFIG_FREERTOS_USE_TRACE_FACILITY=y -CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y -CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y -CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS=y -CONFIG_MBEDTLS_IRAM_8BIT_MEM_ALLOC=y -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=4 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=8 -CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=8 -CONFIG_LWIP_TCP_RECVMBOX_SIZE=8 -CONFIG_LWIP_UDP_RECVMBOX_SIZE=8 -CONFIG_LOG_DEFAULT_LEVEL_ERROR=y diff --git a/ports/esp32/boards/GENERIC_C3/board.json b/ports/esp32/boards/GENERIC_C3/board.json new file mode 100644 index 0000000000..481e66bccd --- /dev/null +++ b/ports/esp32/boards/GENERIC_C3/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "id": "esp32c3", + "images": [ + "esp32c3_devkitmini.jpg" + ], + "mcu": "esp32c3", + "product": "ESP32-C3", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC_C3/manifest.py b/ports/esp32/boards/GENERIC_C3/manifest.py new file mode 100644 index 0000000000..726fe1fb41 --- /dev/null +++ b/ports/esp32/boards/GENERIC_C3/manifest.py @@ -0,0 +1,34 @@ +freeze("$(PORT_DIR)/modules") +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) +freeze("$(MPY_DIR)/ports/esp8266/modules", "ntptime.py") +freeze("$(MPY_DIR)/drivers/dht", "dht.py") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +include("$(MPY_DIR)/extmod/webrepl/manifest.py") +include("$(MPY_DIR)/drivers/neopixel/manifest.py") + +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +freeze("$(MPY_DIR)/drivers/onewire", "onewire.py") + +# upip +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) + +# boardparser +freeze("$(MPY_DIR)/modules/boardparser", "boardparser.py") + +# driver +include("$(MPY_DIR)/modules/driver/manifest.py") + +# modbus +include("$(MPY_DIR)/modules/modbus/manifest.py") + +# ulinksdk +include("$(MPY_DIR)/modules/ulinksdk/manifest.py") + +# http +freeze("$(MPY_DIR)/modules/http", "http.py") + +# ukv +freeze("$(MPY_DIR)/modules/ukv", "kv.py") +# ukv +freeze("$(MPY_DIR)/modules/adaptor/esp32/c3", "systemAdaptor.py") diff --git a/ports/esp32/boards/GENERIC_C3/manifest_release.py b/ports/esp32/boards/GENERIC_C3/manifest_release.py new file mode 100644 index 0000000000..bf2d00ce72 --- /dev/null +++ b/ports/esp32/boards/GENERIC_C3/manifest_release.py @@ -0,0 +1,9 @@ +include("manifest.py") + +freeze("$(MPY_DIR)/modules/urequests", "urequests.py") + +# freeze("$(MPY_LIB_DIR)/micropython/upysh", "upysh.py") +freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple.py") +freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust.py") +freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple2.py") +freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust2.py") diff --git a/ports/esp32/boards/GENERIC_C3/mpconfigboard.cmake b/ports/esp32/boards/GENERIC_C3/mpconfigboard.cmake index ca84ff7dd9..bea64493b1 100644 --- a/ports/esp32/boards/GENERIC_C3/mpconfigboard.cmake +++ b/ports/esp32/boards/GENERIC_C3/mpconfigboard.cmake @@ -3,9 +3,8 @@ set(IDF_TARGET esp32c3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.ble - boards/GENERIC_C3/sdkconfig.board ) if(NOT MICROPY_FROZEN_MANIFEST) - set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/GENERIC_C3/manifest_release.py) endif() diff --git a/ports/esp32/boards/GENERIC_C3/mpconfigboard.h b/ports/esp32/boards/GENERIC_C3/mpconfigboard.h index 09eeb78799..d403e70e46 100644 --- a/ports/esp32/boards/GENERIC_C3/mpconfigboard.h +++ b/ports/esp32/boards/GENERIC_C3/mpconfigboard.h @@ -2,7 +2,6 @@ #define MICROPY_HW_BOARD_NAME "ESP32C3 module" #define MICROPY_HW_MCU_NAME "ESP32C3" -#define MICROPY_SW_VENDOR_NAME "HaaSPython" #define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_PY_MACHINE_DAC (0) diff --git a/ports/esp32/boards/GENERIC_C3/sdkconfig.board b/ports/esp32/boards/GENERIC_C3/sdkconfig.board deleted file mode 100644 index e508e63413..0000000000 --- a/ports/esp32/boards/GENERIC_C3/sdkconfig.board +++ /dev/null @@ -1,35 +0,0 @@ - -CONFIG_MBEDTLS_PSK_MODES=y -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n -CONFIG_PARTITION_TABLE_OFFSET=0x15000 -CONFIG_BOOTLOADER_WDT_TIME_MS=120000 -CONFIG_FATFS_LFN_HEAP=y -CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY=y -CONFIG_FREERTOS_UNICORE=y -CONFIG_HEAP_POISONING_LIGHT=y -CONFIG_HEAP_TRACING_OFF=y -CONFIG_HEAP_TASK_TRACKING=y -CONFIG_FREERTOS_USE_TRACE_FACILITY=y -CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y -CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y -CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS=y -CONFIG_MBEDTLS_IRAM_8BIT_MEM_ALLOC=y -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=4 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=8 -CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=8 -CONFIG_LWIP_TCP_RECVMBOX_SIZE=8 -CONFIG_LWIP_UDP_RECVMBOX_SIZE=8 -CONFIG_LOG_DEFAULT_LEVEL_ERROR=y - -CONFIG_ESP32C3_REV_MIN_3=y -CONFIG_ESP32C3_REV_MIN=3 -CONFIG_ESP32C3_BROWNOUT_DET=y -CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_7= -CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_4=y -CONFIG_ESP32C3_BROWNOUT_DET_LVL=4 -CONFIG_ESP_CONSOLE_UART_DEFAULT=y -CONFIG_ESP_CONSOLE_USB_CDC= -CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG= - -CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 \ No newline at end of file diff --git a/ports/esp32/boards/GENERIC_C3_USB/board.json b/ports/esp32/boards/GENERIC_C3_USB/board.json new file mode 100644 index 0000000000..94d86d4428 --- /dev/null +++ b/ports/esp32/boards/GENERIC_C3_USB/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "id": "esp32c3-usb", + "images": [ + "esp32c3_devkitmini.jpg" + ], + "mcu": "esp32c3", + "product": "ESP32-C3 with USB", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC_C3_USB/mpconfigboard.h b/ports/esp32/boards/GENERIC_C3_USB/mpconfigboard.h index 56b7a793a8..d403e70e46 100644 --- a/ports/esp32/boards/GENERIC_C3_USB/mpconfigboard.h +++ b/ports/esp32/boards/GENERIC_C3_USB/mpconfigboard.h @@ -1,8 +1,7 @@ // This configuration is for a generic ESP32C3 board with 4MiB (or more) of flash. -#define MICROPY_HW_BOARD_NAME "ESP32C3_USB module" -#define MICROPY_HW_MCU_NAME "ESP32-C3" -#define MICROPY_SW_VENDOR_NAME "HaaSPython" +#define MICROPY_HW_BOARD_NAME "ESP32C3 module" +#define MICROPY_HW_MCU_NAME "ESP32C3" #define MICROPY_HW_ENABLE_SDCARD (0) #define MICROPY_PY_MACHINE_DAC (0) diff --git a/ports/esp32/boards/GENERIC_C3_USB/sdkconfig.board b/ports/esp32/boards/GENERIC_C3_USB/sdkconfig.board index df3a8d8e85..f0cbad00e4 100644 --- a/ports/esp32/boards/GENERIC_C3_USB/sdkconfig.board +++ b/ports/esp32/boards/GENERIC_C3_USB/sdkconfig.board @@ -4,6 +4,6 @@ CONFIG_ESP32C3_BROWNOUT_DET=y CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_7= CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_4=y CONFIG_ESP32C3_BROWNOUT_DET_LVL=4 -CONFIG_ESP_CONSOLE_UART_DEFAULT=y +CONFIG_ESP_CONSOLE_UART_DEFAULT= CONFIG_ESP_CONSOLE_USB_CDC= -CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG= +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/ports/esp32/boards/GENERIC_D2WD/board.json b/ports/esp32/boards/GENERIC_D2WD/board.json new file mode 100644 index 0000000000..39fce46bca --- /dev/null +++ b/ports/esp32/boards/GENERIC_D2WD/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "id": "esp32-d2wd", + "images": [ + "generic_d2wd.jpg" + ], + "mcu": "esp32", + "product": "ESP32 D2WD", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC_D2WD/sdkconfig.board b/ports/esp32/boards/GENERIC_D2WD/sdkconfig.board index 367283ded3..07e208a09a 100644 --- a/ports/esp32/boards/GENERIC_D2WD/sdkconfig.board +++ b/ports/esp32/boards/GENERIC_D2WD/sdkconfig.board @@ -1,3 +1,7 @@ +# Optimise using -Os to reduce size +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_PERF=n + CONFIG_ESPTOOLPY_FLASHMODE_DIO=y CONFIG_ESPTOOLPY_FLASHFREQ_40M=y CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y diff --git a/ports/esp32/boards/GENERIC_OTA/board.json b/ports/esp32/boards/GENERIC_OTA/board.json new file mode 100644 index 0000000000..97756a9fb2 --- /dev/null +++ b/ports/esp32/boards/GENERIC_OTA/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "id": "esp32-ota", + "images": [ + "esp32_devkitc.jpg" + ], + "mcu": "esp32", + "product": "ESP32 with OTA support", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC_S2/board.json b/ports/esp32/boards/GENERIC_S2/board.json new file mode 100644 index 0000000000..dbd3b5b015 --- /dev/null +++ b/ports/esp32/boards/GENERIC_S2/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy_s2.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s2.jpg" + ], + "mcu": "esp32s2", + "product": "ESP32-S2", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC_S3/board.json b/ports/esp32/boards/GENERIC_S3/board.json new file mode 100644 index 0000000000..058fb7dff0 --- /dev/null +++ b/ports/esp32/boards/GENERIC_S3/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "ESP32-S3", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC_S3/manifest.py b/ports/esp32/boards/GENERIC_S3/manifest.py new file mode 100644 index 0000000000..a76de53301 --- /dev/null +++ b/ports/esp32/boards/GENERIC_S3/manifest.py @@ -0,0 +1,34 @@ +freeze("$(PORT_DIR)/modules") +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) +freeze("$(MPY_DIR)/ports/esp8266/modules", "ntptime.py") +freeze("$(MPY_DIR)/drivers/dht", "dht.py") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +include("$(MPY_DIR)/extmod/webrepl/manifest.py") +include("$(MPY_DIR)/drivers/neopixel/manifest.py") + +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +freeze("$(MPY_DIR)/drivers/onewire", "onewire.py") + +# upip +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) + +# boardparser +freeze("$(MPY_DIR)/modules/boardparser", "boardparser.py") + +# driver +include("$(MPY_DIR)/modules/driver/manifest.py") + +# modbus +include("$(MPY_DIR)/modules/modbus/manifest.py") + +# ulinksdk +include("$(MPY_DIR)/modules/ulinksdk/manifest.py") + +# http +freeze("$(MPY_DIR)/modules/http", "http.py") + +# ukv +freeze("$(MPY_DIR)/modules/ukv", "kv.py") +# ukv +freeze("$(MPY_DIR)/modules/adaptor/esp32/nodemcu32s", "systemAdaptor.py") diff --git a/ports/esp32/boards/GENERIC_S3/manifest_release.py b/ports/esp32/boards/GENERIC_S3/manifest_release.py new file mode 100644 index 0000000000..bf2d00ce72 --- /dev/null +++ b/ports/esp32/boards/GENERIC_S3/manifest_release.py @@ -0,0 +1,9 @@ +include("manifest.py") + +freeze("$(MPY_DIR)/modules/urequests", "urequests.py") + +# freeze("$(MPY_LIB_DIR)/micropython/upysh", "upysh.py") +freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple.py") +freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust.py") +freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple2.py") +freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust2.py") diff --git a/ports/esp32/boards/GENERIC_S3/mpconfigboard.cmake b/ports/esp32/boards/GENERIC_S3/mpconfigboard.cmake index 2216027123..afcbee6af1 100644 --- a/ports/esp32/boards/GENERIC_S3/mpconfigboard.cmake +++ b/ports/esp32/boards/GENERIC_S3/mpconfigboard.cmake @@ -2,10 +2,11 @@ set(IDF_TARGET esp32s3) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base + boards/sdkconfig.usb boards/sdkconfig.ble boards/GENERIC_S3/sdkconfig.board ) if(NOT MICROPY_FROZEN_MANIFEST) - set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/GENERIC_S3/manifest_release.py) endif() diff --git a/ports/esp32/boards/GENERIC_S3/mpconfigboard.h b/ports/esp32/boards/GENERIC_S3/mpconfigboard.h index 6796f44a3f..3540e5a855 100644 --- a/ports/esp32/boards/GENERIC_S3/mpconfigboard.h +++ b/ports/esp32/boards/GENERIC_S3/mpconfigboard.h @@ -1,8 +1,7 @@ -#define MICROPY_HW_BOARD_NAME "ESP32S3 module" -#define MICROPY_HW_MCU_NAME "ESP32-S3" -#define MICROPY_SW_VENDOR_NAME "HaaSPython" +#define MICROPY_HW_BOARD_NAME "ESP32S3 module" +#define MICROPY_HW_MCU_NAME "ESP32S3" -#define MICROPY_PY_MACHINE_DAC (0) +#define MICROPY_PY_MACHINE_DAC (0) -#define MICROPY_HW_I2C0_SCL (9) -#define MICROPY_HW_I2C0_SDA (8) +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) diff --git a/ports/esp32/boards/GENERIC_S3/sdkconfig.board b/ports/esp32/boards/GENERIC_S3/sdkconfig.board index 32fbe8afdc..c9726d4232 100644 --- a/ports/esp32/boards/GENERIC_S3/sdkconfig.board +++ b/ports/esp32/boards/GENERIC_S3/sdkconfig.board @@ -1,32 +1,12 @@ - -CONFIG_MBEDTLS_PSK_MODES=y -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n -CONFIG_PARTITION_TABLE_OFFSET=0x15000 -CONFIG_BOOTLOADER_WDT_TIME_MS=120000 -CONFIG_FATFS_LFN_HEAP=y -CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY=y -CONFIG_FREERTOS_UNICORE=y -CONFIG_HEAP_POISONING_LIGHT=y -CONFIG_HEAP_TRACING_OFF=y -CONFIG_HEAP_TASK_TRACKING=y -CONFIG_FREERTOS_USE_TRACE_FACILITY=y -CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y -CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y -CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS=y -CONFIG_MBEDTLS_IRAM_8BIT_MEM_ALLOC=y -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=4 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=8 -CONFIG_LOG_DEFAULT_LEVEL_ERROR=y - CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y CONFIG_ESPTOOLPY_AFTER_NORESET=y -CONFIG_ESPTOOLPY_FLASHMODE_DIO=y -CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_SPIRAM_MEMTEST= + CONFIG_ESPTOOLPY_FLASHSIZE_4MB= CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y CONFIG_ESPTOOLPY_FLASHSIZE_16MB= -#CONFIG_PARTITION_TABLE_CUSTOM=y -#CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" \ No newline at end of file +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" diff --git a/ports/esp32/boards/GENERIC_S3_SPIRAM/board.json b/ports/esp32/boards/GENERIC_S3_SPIRAM/board.json new file mode 100644 index 0000000000..ee2cb8d3f4 --- /dev/null +++ b/ports/esp32/boards/GENERIC_S3_SPIRAM/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "Generic ESP32-S3 (SPIRAM)", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.cmake b/ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.cmake new file mode 100644 index 0000000000..682c31456f --- /dev/null +++ b/ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.cmake @@ -0,0 +1,12 @@ +set(IDF_TARGET esp32s3) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.usb + boards/sdkconfig.spiram_sx + boards/GENERIC_S3_SPIRAM/sdkconfig.board +) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) +endif() diff --git a/ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.h b/ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.h new file mode 100644 index 0000000000..beb796dd9e --- /dev/null +++ b/ports/esp32/boards/GENERIC_S3_SPIRAM/mpconfigboard.h @@ -0,0 +1,8 @@ +#define MICROPY_HW_BOARD_NAME "ESP32S3 module (spiram)" +#define MICROPY_HW_MCU_NAME "ESP32S3" + +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_PY_MACHINE_DAC (0) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) diff --git a/ports/esp32/boards/GENERIC_S3_SPIRAM/sdkconfig.board b/ports/esp32/boards/GENERIC_S3_SPIRAM/sdkconfig.board new file mode 100644 index 0000000000..c9726d4232 --- /dev/null +++ b/ports/esp32/boards/GENERIC_S3_SPIRAM/sdkconfig.board @@ -0,0 +1,12 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST= + +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB= +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" diff --git a/ports/esp32/boards/M5STACK_CORE2/board.json b/ports/esp32/boards/GENERIC_SPIRAM/board.json similarity index 88% rename from ports/esp32/boards/M5STACK_CORE2/board.json rename to ports/esp32/boards/GENERIC_SPIRAM/board.json index c0efc22e3f..afb57b2ed1 100644 --- a/ports/esp32/boards/M5STACK_CORE2/board.json +++ b/ports/esp32/boards/GENERIC_SPIRAM/board.json @@ -9,7 +9,9 @@ "WiFi" ], "id": "esp32spiram", - "images": [], + "images": [ + "esp32_psram.jpg" + ], "mcu": "esp32", "product": "ESP32 with SPIRAM", "thumbnail": "", diff --git a/ports/esp32/boards/M5STACK_CORE2/board.md b/ports/esp32/boards/GENERIC_SPIRAM/board.md similarity index 100% rename from ports/esp32/boards/M5STACK_CORE2/board.md rename to ports/esp32/boards/GENERIC_SPIRAM/board.md diff --git a/ports/esp32/boards/LOLIN_S2_MINI/board.json b/ports/esp32/boards/LOLIN_S2_MINI/board.json new file mode 100644 index 0000000000..41e62a0228 --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_MINI/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy_s2.md" + ], + "docs": "", + "features": [ + "SPIRAM", + "USB-C", + "WiFi" + ], + "images": [ + "lolin_s2_mini.jpg" + ], + "mcu": "esp32s2", + "product": "S2 mini", + "thumbnail": "", + "url": "https://www.wemos.cc/en/latest/s2/s2_mini.html", + "vendor": "Wemos" +} diff --git a/ports/esp32/boards/LOLIN_S2_MINI/manifest.py b/ports/esp32/boards/LOLIN_S2_MINI/manifest.py new file mode 100644 index 0000000000..f993d4fa6b --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_MINI/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("./modules") diff --git a/ports/esp32/boards/LOLIN_S2_MINI/modules/s2mini.py b/ports/esp32/boards/LOLIN_S2_MINI/modules/s2mini.py new file mode 100644 index 0000000000..4fc038c81a --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_MINI/modules/s2mini.py @@ -0,0 +1,31 @@ +# LOLIN S2 MINI MicroPython Helper Library + +from micropython import const +from machine import Pin + +# Pin Assignments + +# SPI +SPI_MOSI = const(11) +SPI_MISO = const(9) +SPI_CLK = const(7) + +# I2C +I2C_SDA = const(33) +I2C_SCL = const(35) + +# DAC +DAC1 = const(17) +DAC2 = const(18) + +# LED +LED = const(15) + +# BUTTON +BUTTON = const(0) + +# Helper methods for built in sensors + +led = Pin(LED, Pin.OUT, value=0) + +button = Pin(BUTTON, Pin.IN, Pin.PULL_UP) diff --git a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake new file mode 100644 index 0000000000..5f157e7e77 --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.cmake @@ -0,0 +1,11 @@ +set(IDF_TARGET esp32s2) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.spiram_sx + boards/sdkconfig.usb +) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) +endif() diff --git a/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h new file mode 100644 index 0000000000..e0ef10d1db --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_MINI/mpconfigboard.h @@ -0,0 +1,12 @@ +#define MICROPY_HW_BOARD_NAME "LOLIN_S2_MINI" +#define MICROPY_HW_MCU_NAME "ESP32-S2FN4R2" + +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_HW_ENABLE_SDCARD (0) + +#define MICROPY_HW_I2C0_SCL (35) +#define MICROPY_HW_I2C0_SDA (33) + +#define MICROPY_HW_SPI1_MOSI (11) +#define MICROPY_HW_SPI1_MISO (9) +#define MICROPY_HW_SPI1_SCK (7) diff --git a/ports/esp32/boards/LOLIN_S2_MINI/sdkconfig.board b/ports/esp32/boards/LOLIN_S2_MINI/sdkconfig.board new file mode 100644 index 0000000000..1a7ef3f8b9 --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_MINI/sdkconfig.board @@ -0,0 +1,6 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_USB_AND_UART=y +# LWIP +CONFIG_LWIP_LOCAL_HOSTNAME="LOLIN_S2_MINI" +# end of LWIP diff --git a/ports/esp32/boards/LOLIN_S2_PICO/board.json b/ports/esp32/boards/LOLIN_S2_PICO/board.json new file mode 100644 index 0000000000..43322b87b0 --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_PICO/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy_s2.md" + ], + "docs": "", + "features": [ + "Breadboard Friendly", + "OLED", + "SPIRAM", + "STEMMA QT/QWIIC", + "USB-C", + "WiFi" + ], + "images": [ + "lolin_s2_pico.jpg" + ], + "mcu": "esp32s2", + "product": "S2 pico", + "thumbnail": "", + "url": "https://www.wemos.cc/en/latest/s2/s2_pico.html", + "vendor": "Wemos" +} diff --git a/ports/esp32/boards/LOLIN_S2_PICO/manifest.py b/ports/esp32/boards/LOLIN_S2_PICO/manifest.py new file mode 100644 index 0000000000..98d4247c60 --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_PICO/manifest.py @@ -0,0 +1,4 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("./modules") + +freeze("$(MPY_DIR)/drivers/display", "ssd1306.py") diff --git a/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico.py b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico.py new file mode 100644 index 0000000000..be59db715e --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico.py @@ -0,0 +1,38 @@ +# LOLIN S2 PICO MicroPython Helper Library + +from micropython import const +from machine import Pin, I2C, Signal +from s2pico_oled import OLED + +# Pin Assignments + +# SPI +SPI_MOSI = const(35) +SPI_MISO = const(36) +SPI_CLK = const(37) + +# I2C +I2C_SDA = const(8) +I2C_SCL = const(9) + +# DAC +DAC1 = const(17) +DAC2 = const(18) + +# LED +LED = const(10) + +# OLED +OLED_RST = const(18) + +# BUTTON +BUTTON = const(0) + +# Helper methods for built in sensors + +led = Signal(LED, Pin.OUT, value=0, invert=True) + +button = Pin(BUTTON, Pin.IN, Pin.PULL_UP) + +i2c = I2C(0) +oled = OLED(i2c, Pin(OLED_RST)) diff --git a/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py new file mode 100644 index 0000000000..37dc5a340f --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py @@ -0,0 +1,48 @@ +from time import sleep_ms +from ssd1306 import SSD1306_I2C +import network + + +class OLED(SSD1306_I2C): + def __init__(self, i2c, reset): + reset.init(reset.OUT, value=1) + self._reset = reset + self.reset(False) + super().__init__(128, 32, i2c) + + def reset(self, reinit=True): + self._reset(1) + sleep_ms(1) + self._reset(0) + sleep_ms(10) + self._reset(1) + if reinit: + self.init_display() + + def test(self): + self.fill(0) + self.fill_rect(0, 0, 32, 32, 1) + self.fill_rect(2, 2, 28, 28, 0) + self.vline(9, 8, 22, 1) + self.vline(16, 2, 22, 1) + self.vline(23, 8, 22, 1) + self.fill_rect(26, 24, 2, 4, 1) + self.text("MicroPython", 40, 0, 1) + self.text("SSD1306", 40, 12, 1) + self.text("OLED 128x32", 40, 24, 1) + self.show() + + def display_wifi(self): + self.fill(0) + self.text("Scan...", 0, 0, 1) + self.show() + + sta_if = network.WLAN(network.STA_IF) + sta_if.active(True) + _wifi = sta_if.scan() + + self.fill(0) + self.text(str(len(_wifi)) + " Networks", 0, 0, 1) + self.text(str(_wifi[0][3]) + " " + (_wifi[0][0]).decode("utf-8"), 0, 12, 1) + self.text(str(_wifi[1][3]) + " " + (_wifi[1][0]).decode("utf-8"), 0, 24, 1) + self.show() diff --git a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake new file mode 100644 index 0000000000..5f157e7e77 --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.cmake @@ -0,0 +1,11 @@ +set(IDF_TARGET esp32s2) + +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.spiram_sx + boards/sdkconfig.usb +) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) +endif() diff --git a/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h new file mode 100644 index 0000000000..549dd9847c --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_PICO/mpconfigboard.h @@ -0,0 +1,12 @@ +#define MICROPY_HW_BOARD_NAME "LOLIN_S2_PICO" +#define MICROPY_HW_MCU_NAME "ESP32-S2FN4R2" + +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_HW_ENABLE_SDCARD (0) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) + +#define MICROPY_HW_SPI1_MOSI (35) +#define MICROPY_HW_SPI1_MISO (36) +#define MICROPY_HW_SPI1_SCK (37) diff --git a/ports/esp32/boards/LOLIN_S2_PICO/sdkconfig.board b/ports/esp32/boards/LOLIN_S2_PICO/sdkconfig.board new file mode 100644 index 0000000000..bf0f3e780e --- /dev/null +++ b/ports/esp32/boards/LOLIN_S2_PICO/sdkconfig.board @@ -0,0 +1,6 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_USB_AND_UART=y +# LWIP +CONFIG_LWIP_LOCAL_HOSTNAME="LOLIN_S2_PICO" +# end of LWIP diff --git a/ports/esp32/boards/M5STACK_ATOM/board.json b/ports/esp32/boards/M5STACK_ATOM/board.json new file mode 100644 index 0000000000..9d1886887c --- /dev/null +++ b/ports/esp32/boards/M5STACK_ATOM/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Grove", + "IMU", + "Infrared", + "RGB LED", + "USB-C", + "WiFi" + ], + "images": [ + "m5stack_atom.jpg" + ], + "mcu": "esp32", + "product": "M5 Stack Atom", + "thumbnail": "", + "url": "https://m5stack.com/", + "vendor": "M5 Stack" +} diff --git a/ports/esp32/boards/M5STACK_CORE2/mpconfigboard.cmake b/ports/esp32/boards/M5STACK_CORE2/mpconfigboard.cmake deleted file mode 100644 index 409dbf1722..0000000000 --- a/ports/esp32/boards/M5STACK_CORE2/mpconfigboard.cmake +++ /dev/null @@ -1,14 +0,0 @@ -set(IDF_TARGET esp32) - -set(SDKCONFIG_DEFAULTS - boards/sdkconfig.base - boards/sdkconfig.ble - boards/sdkconfig.spiram - boards/sdkconfig.240mhz - boards/M5STACK_CORE2/sdkconfig.board - boards/M5STACK_CORE2/sdkconfig.lvgl -) - -if(NOT MICROPY_FROZEN_MANIFEST) - set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) -endif() diff --git a/ports/esp32/boards/M5STACK_CORE2/mpconfigboard.h b/ports/esp32/boards/M5STACK_CORE2/mpconfigboard.h deleted file mode 100644 index 9fb7131c57..0000000000 --- a/ports/esp32/boards/M5STACK_CORE2/mpconfigboard.h +++ /dev/null @@ -1,4 +0,0 @@ -#define MICROPY_HW_BOARD_NAME "ESP32 module (spiram)" -#define MICROPY_HW_MCU_NAME "ESP32" -#define MICROPY_HW_BOARD_TYPE "8M" -#define MICROPY_SW_VENDOR_NAME "HaaSPython" diff --git a/ports/esp32/boards/M5STACK_CORE2/sdkconfig.board b/ports/esp32/boards/M5STACK_CORE2/sdkconfig.board deleted file mode 100644 index fd2ba3b7a9..0000000000 --- a/ports/esp32/boards/M5STACK_CORE2/sdkconfig.board +++ /dev/null @@ -1,57 +0,0 @@ -# 16 MB flash - -CONFIG_ESPTOOLPY_FLASHSIZE_4MB= -CONFIG_ESPTOOLPY_FLASHSIZE_8MB= -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y -CONFIG_ESPTOOLPY_FLASHSIZE="16MB" -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16MiB-priv.csv" - -CONFIG_MBEDTLS_PSK_MODES=y -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n -CONFIG_BOOTLOADER_WDT_TIME_MS=120000 -CONFIG_FATFS_LFN_HEAP=y - -CONFIG_PARTITION_TABLE_OFFSET=0x15000 - -# Memory configuration -#CONFIG_SPIRAM_TYPE_ESPPSRAM64=y -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=256 -#CONFIG_SPIRAM_USE_CAPS_ALLOC=y -CONFIG_SPIRAM_USE_MALLOC=y -CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y - -CONFIG_ESP_DISPATCHER_DELEGATE_TASK_CORE=0 -CONFIG_ESP_DISPATCHER_DELEGATE_TASK_PRIO=10 -CONFIG_ESP_DISPATCHER_DELEGATE_STACK_SIZE=4096 - -CONFIG_HEAP_POISONING_LIGHT=y -CONFIG_HEAP_TRACING_OFF=y -CONFIG_HEAP_TASK_TRACKING=y -CONFIG_FREERTOS_USE_TRACE_FACILITY=y -CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y -CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y -CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS=y - -# Memory optimization, based on following URL: -# https://blog.csdn.net/espressif/article/details/115469094 -# -# 启用 TLS 非对称 IN/OUT 内容长度 -CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y -CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN = 2048 - -# 允许对 mbedTLS 使用动态缓冲策略 -CONFIG_MBEDTLS_DYNAMIC_BUFFER=y -CONFIG_MBEDTLS_DYNAMIC_FREE_PEER_CERT=y -CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y - -# 配置MBEDTLS 使用外部内存 -CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y - -# m5stackcore2 和nodemcu32s的wifi和lwip的配置保持一致,减少20KB内存 -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=4 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=8 -CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=8 -CONFIG_LWIP_TCP_RECVMBOX_SIZE=8 -CONFIG_LWIP_UDP_RECVMBOX_SIZE=8 diff --git a/ports/esp32/boards/M5STACK_CORE2/sdkconfig.lvgl b/ports/esp32/boards/M5STACK_CORE2/sdkconfig.lvgl deleted file mode 100644 index 02a2f1036b..0000000000 --- a/ports/esp32/boards/M5STACK_CORE2/sdkconfig.lvgl +++ /dev/null @@ -1,183 +0,0 @@ -# Power system configuration(AXP192) -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT6_SELECTED=y -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT4_SELECTED=y -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT2_SELECTED=y -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT1_SELECTED=y -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT0_SELECTED=y -CONFIG_AXP192_EXTEN_DCDC2_CONTROL_BIT2=0x04 -CONFIG_AXP192_EXTEN_DCDC2_CONTROL_BIT0=0x01 -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT6=0x40 -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT4=0x10 -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT3=0x00 -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT2=0x04 -CONFIG_AXP192_GPIO0_CONTROL_BIT20=0x07 -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT1=0x02 -CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT0=0x01 -CONFIG_AXP192_GPIO0_LDOIO0_VOLTAGE_BIT74=0x00 -CONFIG_AXP192_LDO23_VOLTAGE_BIT74=0x00 -CONFIG_AXP192_LDO23_VOLTAGE_BIT30=0x00 -CONFIG_AXP192_DCDC1_VOLTAGE_BIT60=0x68 -CONFIG_AXP192_DCDC3_VOLTAGE_BIT60=0x54 -CONFIG_AXP192_LDO23_VOLTAGE_BIT74_3V3=y -CONFIG_AXP192_LDO23_VOLTAGE_BIT30_2V0=y -CONFIG_AXP192_DCDC1_VOLTAGE_BIT60_3V3=y -CONFIG_AXP192_DCDC3_VOLTAGE_BIT60_2V8=y -CONFIG_AXP192_ADC_ENABLE_1_BIT7_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT6_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT5_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT4_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT3_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT2_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT1_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT0_SELECTED=y -CONFIG_AXP192_ADC_ENABLE_1_BIT7=0x80 -CONFIG_AXP192_ADC_ENABLE_1_BIT6=0x40 -CONFIG_AXP192_ADC_ENABLE_1_BIT5=0x20 -CONFIG_AXP192_ADC_ENABLE_1_BIT4=0x10 -CONFIG_AXP192_ADC_ENABLE_1_BIT3=0x08 -CONFIG_AXP192_ADC_ENABLE_1_BIT2=0x04 -CONFIG_AXP192_ADC_ENABLE_1_BIT1=0x02 -CONFIG_AXP192_ADC_ENABLE_1_BIT0=0x01 -CONFIG_AXP192_CHARGE_CONTROL_1_BIT7_SELECTED=y -CONFIG_AXP192_CHARGE_CONTROL_1_BIT7=0x80 -CONFIG_AXP192_CHARGE_CONTROL_1_BIT65=0x40 -CONFIG_AXP192_CHARGE_CONTROL_1_BIT30=0x00 -CONFIG_AXP192_CHARGE_CONTROL_1_BIT4=0x10 -CONFIG_AXP192_CHARGE_CONTROL_1_BIT65_42_SELECTED=y -CONFIG_AXP192_CHARGE_CONTROL_1_BIT30_100_SELECTED=y -CONFIG_AXP192_CHARGE_CONTROL_1_BIT4_15_SELECTED=y -CONFIG_AXP192_BATTERY_CHARGE_CONTROL_BIT7=0x00 -CONFIG_AXP192_BATTERY_CHARGE_CONTROL_BIT65=0x00 -CONFIG_AXP192_BATTERY_CHARGE_CONTROL_BIT10=0x00 -CONFIG_AXP192_GPIO1_CONTROL_BIT20_NMOS_SELECTED=y -CONFIG_AXP192_GPIO1_CONTROL_BIT20=0x00 -CONFIG_AXP192_GPIO2_CONTROL_BIT20_NMOS_SELECTED=y -CONFIG_AXP192_GPIO2_CONTROL_BIT20=0x00 -CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT10_NMOS_SELECTED=y -CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT10=0x01 -CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT32_NMOS_SELECTED=y -CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT32=0x04 -CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT7=0x80 - -# I2C Port Settings -CONFIG_HAVE_I2C_MANAGER=y -CONFIG_I2C_MANAGER_0_ENABLED=y -CONFIG_I2C_MANAGER_0_SDA=21 -CONFIG_I2C_MANAGER_0_SCL=22 -CONFIG_I2C_MANAGER_0_FREQ_HZ=400000 -CONFIG_I2C_MANAGER_0_TIMEOUT=20 -CONFIG_I2C_MANAGER_0_LOCK_TIMEOUT=50 -CONFIG_I2C_MANAGER_0_PULLUPS=y -CONFIG_I2C_MANAGER_1_ENABLED=y -CONFIG_I2C_MANAGER_1_SDA=32 -CONFIG_I2C_MANAGER_1_SCL=33 -CONFIG_I2C_MANAGER_1_FREQ_HZ=4000000 -CONFIG_I2C_MANAGER_1_TIMEOUT=20 -CONFIG_I2C_MANAGER_1_LOCK_TIMEOUT=50 -CONFIG_I2C_MANAGER_1_PULLUPS=y - -# LVGL configuration -CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y -CONFIG_LV_COLOR_DEPTH_16=y -CONFIG_LV_COLOR_DEPTH=16 -CONFIG_LV_COLOR_16_SWAP=y -CONFIG_LV_COLOR_CHROMA_KEY_HEX=0x00FF00 -CONFIG_LV_MEM_SIZE_KILOBYTES=32 -CONFIG_LV_MEMCPY_MEMSET_STD=y -# Should be changed from /components/py_engine/engine/lib/lv_bindings/lvgl/Kconfig -# CONFIG_LV_DISP_DEF_REFR_PERIOD=20 -CONFIG_LV_INDEV_DEF_READ_PERIOD=30 -CONFIG_LV_DPI_DEF=130 -CONFIG_LV_DRAW_COMPLEX=y -CONFIG_LV_SHADOW_CACHE_SIZE=0 -CONFIG_LV_IMG_CACHE_DEF_SIZE=0 -CONFIG_LV_DISP_ROT_MAX_BUF=10240 -CONFIG_LV_USE_ASSERT_NULL=y -CONFIG_LV_USE_ASSERT_MALLOC=y -CONFIG_LV_USE_PERF_MONITOR=y -CONFIG_LV_USE_USER_DATA=y -CONFIG_LV_FONT_MONTSERRAT_12=y -CONFIG_LV_FONT_MONTSERRAT_14=y -CONFIG_LV_FONT_MONTSERRAT_16=y -CONFIG_LV_FONT_MONTSERRAT_18=y -CONFIG_LV_FONT_MONTSERRAT_20=y -CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16=y -CONFIG_LV_USE_THEME_DEFAULT=y -CONFIG_LV_THEME_DEFAULT_DARK=y -CONFIG_LV_THEME_DEFAULT_GROW=y -CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80 -CONFIG_LV_USE_THEME_BASIC=y -CONFIG_LV_TXT_ENC_UTF8=y -CONFIG_LV_TXT_BREAK_CHARS="=,.;:-_" -CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 -CONFIG_LV_TXT_COLOR_CMD="#" -CONFIG_LV_USE_ARC=y -CONFIG_LV_USE_BAR=y -CONFIG_LV_USE_BTN=y -CONFIG_LV_USE_BTNMATRIX=y -CONFIG_LV_USE_CANVAS=y -CONFIG_LV_USE_CHECKBOX=y -CONFIG_LV_USE_CHART=y -CONFIG_LV_USE_DROPDOWN=y -CONFIG_LV_USE_IMG=y -CONFIG_LV_USE_LABEL=y -CONFIG_LV_USE_LINE=y -CONFIG_LV_USE_METER=y -CONFIG_LV_USE_ROLLER=y -CONFIG_LV_ROLLER_INF_PAGES=7 -CONFIG_LV_USE_SLIDER=y -CONFIG_LV_USE_SWITCH=y -CONFIG_LV_USE_TEXTAREA=y -CONFIG_LV_TEXTAREA_DEF_PWN_SHOW_TIME=1500 -CONFIG_LV_USE_TABLE=y -CONFIG_LV_USE_ANIMIMG=y -CONFIG_LV_USE_CALENDAR=y -CONFIG_LV_CALENDAR_WEEK_STARTS_MONDAY=y -CONFIG_LV_USE_CALENDAR_HEADER_ARROW=y -CONFIG_LV_USE_CALENDAR_HEADER_DROPDOWN=y -CONFIG_LV_USE_COLORWHEEL=y -CONFIG_LV_USE_IMGBTN=y -CONFIG_LV_USE_KEYBOARD=y -CONFIG_LV_USE_LED=y -CONFIG_LV_USE_LIST=y -CONFIG_LV_USE_MSGBOX=y -CONFIG_LV_USE_SPINBOX=y -CONFIG_LV_USE_SPINNER=y -CONFIG_LV_USE_TABVIEW=y -CONFIG_LV_USE_TILEVIEW=y -CONFIG_LV_USE_WIN=y -CONFIG_LV_USE_SPAN=y -CONFIG_LV_USE_FLEX=y -CONFIG_LV_USE_GRID=y -CONFIG_LV_PREDEFINED_DISPLAY_M5CORE2=y -CONFIG_LV_TFT_DISPLAY_CONTROLLER_ILI9341=y -CONFIG_LV_TFT_DISPLAY_PROTOCOL_SPI=y -CONFIG_LV_DISPLAY_ORIENTATION_PORTRAIT=y -CONFIG_LV_DISPLAY_ORIENTATION=0 -CONFIG_CUSTOM_DISPLAY_BUFFER_SIZE=y -CONFIG_CUSTOM_DISPLAY_BUFFER_BYTES=12800 -CONFIG_LV_TFT_DISPLAY_SPI_HSPI=y -CONFIG_LV_TFT_DISPLAY_SPI_TRANS_MODE_SIO=y -CONFIG_LV_TFT_DISPLAY_SPI_HALF_DUPLEX=y -CONFIG_LV_TFT_USE_CUSTOM_SPI_CLK_DIVIDER=y -CONFIG_LV_TFT_SPI_CLK_DIVIDER_2=y -CONFIG_LV_TFT_CUSTOM_SPI_CLK_DIVIDER=2 -CONFIG_LV_INVERT_COLORS=y -CONFIG_LV_DISP_SPI_MOSI=23 -CONFIG_LV_DISPLAY_USE_SPI_MISO=y -CONFIG_LV_DISP_SPI_MISO=38 -CONFIG_LV_DISP_SPI_INPUT_DELAY_NS=0 -CONFIG_LV_DISP_SPI_CLK=18 -CONFIG_LV_DISPLAY_USE_SPI_CS=y -CONFIG_LV_DISP_SPI_CS=5 -CONFIG_LV_DISPLAY_USE_DC=y -CONFIG_LV_DISP_PIN_DC=15 -CONFIG_LV_DISP_PIN_BUSY=35 -CONFIG_LV_DISP_BACKLIGHT_OFF=y -CONFIG_LV_I2C=y -CONFIG_LV_I2C_DISPLAY_PORT=0 -CONFIG_LV_TOUCH_CONTROLLER=2 -CONFIG_LV_TOUCH_CONTROLLER_FT6X06=y -CONFIG_LV_I2C_TOUCH=y -CONFIG_LV_I2C_TOUCH_PORT_0=y -CONFIG_LV_I2C_TOUCH_PORT=0 diff --git a/ports/esp32/boards/SIL_WESP32/board.json b/ports/esp32/boards/SIL_WESP32/board.json new file mode 100644 index 0000000000..050620d618 --- /dev/null +++ b/ports/esp32/boards/SIL_WESP32/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "Ethernet", + "PoE", + "WiFi" + ], + "id": "wesp32", + "images": [ + "wesp32-iso.jpg", + "wesp32-top.jpg" + ], + "mcu": "esp32", + "product": "SIL WESP32", + "thumbnail": "", + "url": "https://wesp32.com/", + "vendor": "Silicognition LLC" +} diff --git a/ports/esp32/boards/UM_FEATHERS2/board.json b/ports/esp32/boards/UM_FEATHERS2/board.json new file mode 100644 index 0000000000..fa5b213db0 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/board.json @@ -0,0 +1,27 @@ +{ + "deploy": [ + "deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "Feather", + "RGB LED", + "SPIRAM", + "STEMMA QT/QWIIC", + "USB-C", + "WiFi" + ], + "features_non_filterable": [ + "Second LDO" + ], + "id": "featherS2", + "images": [ + "unexpectedmaker_feathers2.jpg" + ], + "mcu": "esp32s2", + "product": "Feather S2", + "thumbnail": "", + "url": "https://feathers2.io/", + "vendor": "Unexpected Maker" +} diff --git a/ports/esp32/boards/UM_FEATHERS2/board.md b/ports/esp32/boards/UM_FEATHERS2/board.md new file mode 100644 index 0000000000..e04a8936c8 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/board.md @@ -0,0 +1,2 @@ +The following files are daily firmware for the FeatherS2. This firmware is +compiled using ESP-IDF v4.3 or later. diff --git a/ports/esp32/boards/UM_FEATHERS2/deploy.md b/ports/esp32/boards/UM_FEATHERS2/deploy.md new file mode 100644 index 0000000000..24bced3cda --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/deploy.md @@ -0,0 +1,50 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +To flash or erase your FeatherS2, you have to first put it into download mode. +To do this, follow these steps: + +- Press and hold the [BOOT] button +- Press and release the [RESET] button +- Release the [BOOT] button + +Now the board is in download mode and the native USB will have enumerated as a serial device. + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +### Linux +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash +``` + +### Mac +```bash +esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 erase_flash +``` + +### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32s2 --port COM(X) erase_flash +``` + +Now download the version of the firmware you would like to install from the options +below, then use the following command to program the firmware starting at address +0x1000, remembering to replace `feathers2-micropython-firmware-version.bin` with the +name of the firmware you just downloaded: + +### Linux +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 feathers2-micropython-firmware-version.bin +``` + +### Mac +```bash +esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 write_flash -z 0x1000 feathers2-micropython-firmware-version.bin +``` + +### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32s2 --port COM(X) write_flash -z 0x1000 feathers2-micropython-firmware-version.bin +``` diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/board.json b/ports/esp32/boards/UM_FEATHERS2NEO/board.json new file mode 100644 index 0000000000..d809485e86 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/board.json @@ -0,0 +1,27 @@ +{ + "deploy": [ + "deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "Feather", + "RGB LED", + "SPIRAM", + "STEMMA QT/QWIIC", + "USB-C", + "WiFi" + ], + "features_non_filterable": [ + "5x5 RGB LED Matrix" + ], + "id": "featherS2neo", + "images": [ + "FeatherS2_Neo_White_Product2.jpg" + ], + "mcu": "esp32s2", + "product": "Feather S2 Neo", + "thumbnail": "", + "url": "https://unexpectedmaker.com/feathers2-neo", + "vendor": "Unexpected Maker" +} diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/board.md b/ports/esp32/boards/UM_FEATHERS2NEO/board.md new file mode 100644 index 0000000000..60ee024a12 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/board.md @@ -0,0 +1,2 @@ +The following files are daily firmware for the FeatherS2 Neo. This firmware is +compiled using ESP-IDF v4.3 or later. diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/deploy.md b/ports/esp32/boards/UM_FEATHERS2NEO/deploy.md new file mode 100644 index 0000000000..b1a116e807 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/deploy.md @@ -0,0 +1,50 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +To flash or erase your FeatherS2 Neo, you have to first put it into download mode. +To do this, follow these steps: + +- Press and hold the [BOOT] button +- Press and release the [RESET] button +- Release the [BOOT] button + +Now the board is in download mode and the native USB will have enumerated as a serial device. + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +### Linux +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash +``` + +### Mac +```bash +esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 erase_flash +``` + +### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32s2 --port COM(X) erase_flash +``` + +Now download the version of the firmware you would like to install from the options below, +then use the following command to program the firmware starting at address 0x1000, +remembering to replace `feathers2neo-micropython-firmware-version.bin` with the name of +the firmware you just downloaded: + +### Linux +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 feathers2neo-micropython-firmware-version.bin +``` + +### Mac +```bash +esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 write_flash -z 0x1000 feathers2neo-micropython-firmware-version.bin +``` + +### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32s2 --port COM(X) write_flash -z 0x1000 feathers2-feathers2neo-firmware-version.bin +``` diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/manifest.py b/ports/esp32/boards/UM_FEATHERS2NEO/manifest.py new file mode 100644 index 0000000000..7ae2ed15d9 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/modules/feathers2neo.py b/ports/esp32/boards/UM_FEATHERS2NEO/modules/feathers2neo.py new file mode 100644 index 0000000000..857c7559d1 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/modules/feathers2neo.py @@ -0,0 +1,90 @@ +# FeatherS2 Neo MicroPython Helper Library +# 2021 Seon Rozenblum - Unexpected Maker +# +# Project home: +# https://unexpectedmaker.com/feathers2-neo +# +# 2021-Sep-04 - v0.1 - Initial implementation + +# Import required libraries +from micropython import const +from machine import Pin, ADC +import machine, time + +# FeatherS2 Neo Hardware Pin Assignments + +# Sense Pins +VBUS_SENSE = const(34) +VBAT_SENSE = const(2) + +# RGB LED Pins +RGB_DATA = const(40) +RGB_PWR = const(39) + +# RGB MATRIX LED Pins +RGB_MATRIX_DATA = const(21) +RGB_MATRIX_PWR = const(4) + +# SPI +SPI_MOSI = const(35) +SPI_MISO = const(37) +SPI_CLK = const(36) + +# I2C +I2C_SDA = const(8) +I2C_SCL = const(9) + +# DAC +DAC1 = const(17) +DAC2 = const(18) + +# Helper functions +def set_pixel_power(state): + """Enable or Disable power to the onboard NeoPixel to either show colour, or to reduce power for deep sleep.""" + Pin(RGB_PWR, Pin.OUT).value(state) + + +def set_pixel_matrix_power(state): + """Enable or Disable power to the onboard NeoPixel RGB Matrix to either show colours, or to reduce power for deep sleep.""" + Pin(RGB_MATRIX_PWR, Pin.OUT).value(state) + + +def get_battery_voltage(): + """ + Returns the current battery voltage. If no battery is connected, returns 4.2V which is the charge voltage + This is an approximation only, but useful to detect if the charge state of the battery is getting low. + """ + adc = ADC(Pin(VBAT_SENSE)) # Assign the ADC pin to read + measuredvbat = adc.read() # Read the value + measuredvbat /= 8192 # divide by 8192 as we are using the default ADC voltage range of 0-1V + measuredvbat *= 4.2 # Multiply by 4.2V, our reference voltage + return round(measuredvbat, 2) + + +def get_vbus_present(): + """Detect if VBUS (5V) power source is present""" + return Pin(VBUS_SENSE, Pin.IN).value() == 1 + + +# NeoPixel rainbow colour wheel +def rgb_color_wheel(wheel_pos): + """Color wheel to allow for cycling through the rainbow of RGB colors.""" + wheel_pos = wheel_pos % 255 + + if wheel_pos < 85: + return 255 - wheel_pos * 3, 0, wheel_pos * 3 + elif wheel_pos < 170: + wheel_pos -= 85 + return 0, wheel_pos * 3, 255 - wheel_pos * 3 + else: + wheel_pos -= 170 + return wheel_pos * 3, 255 - wheel_pos * 3, 0 + + +# Go into deep sleep but shut down the RGB LED first to save power +# Use this if you want lowest deep sleep current +def go_deepsleep(t): + """Deep sleep helper that also powers down the on-board NeoPixel.""" + set_pixel_power(False) + set_pixel_matrix_power(False) + machine.deepsleep(t) diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake new file mode 100644 index 0000000000..b0b3e3aa99 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.cmake @@ -0,0 +1,8 @@ +set(IDF_TARGET esp32s2) +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.spiram_sx + boards/sdkconfig.usb +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) \ No newline at end of file diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h new file mode 100644 index 0000000000..5ee6874b87 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/mpconfigboard.h @@ -0,0 +1,12 @@ +#define MICROPY_HW_BOARD_NAME "FeatherS2 Neo" +#define MICROPY_HW_MCU_NAME "ESP32-S2FN4R2" + +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_HW_ENABLE_SDCARD (0) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) + +#define MICROPY_HW_SPI1_MOSI (35) // SDO +#define MICROPY_HW_SPI1_MISO (37) // SDI +#define MICROPY_HW_SPI1_SCK (36) diff --git a/ports/esp32/boards/UM_FEATHERS2NEO/sdkconfig.board b/ports/esp32/boards/UM_FEATHERS2NEO/sdkconfig.board new file mode 100644 index 0000000000..87a92892d0 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2NEO/sdkconfig.board @@ -0,0 +1,8 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_USB_AND_UART=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +# LWIP +CONFIG_LWIP_LOCAL_HOSTNAME="UMFeatherS2Neo" +# end of LWIP diff --git a/ports/esp32/boards/UM_TINYPICO/board.json b/ports/esp32/boards/UM_TINYPICO/board.json new file mode 100644 index 0000000000..9f1783a9f1 --- /dev/null +++ b/ports/esp32/boards/UM_TINYPICO/board.json @@ -0,0 +1,29 @@ +{ + "deploy": [ + "deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "BLE", + "RGB LED", + "SPIRAM", + "USB-C", + "WiFi" + ], + "features_non_filterable": [ + "TinyPICO Compatible" + ], + "id": "tinypico", + "images": [ + "tinypico-v2-both.jpg" + ], + "mcu": "esp32", + "product": "TinyPICO", + "thumbnail": "", + "url": "https://www.tinypico.com/", + "variants": { + "idf3": "Compiled with IDF 3.x" + }, + "vendor": "Unexpected Maker" +} diff --git a/ports/esp32/boards/UM_TINYPICO/board.md b/ports/esp32/boards/UM_TINYPICO/board.md new file mode 100644 index 0000000000..d0b1266d2e --- /dev/null +++ b/ports/esp32/boards/UM_TINYPICO/board.md @@ -0,0 +1,3 @@ +The following files are daily firmware for the TinyPICO. This firmware is compiled +using ESP-IDF v4.2 or later. Some older releases are also provided that are +compiled with ESP-IDF v3.x. diff --git a/ports/esp32/boards/UM_TINYPICO/deploy.md b/ports/esp32/boards/UM_TINYPICO/deploy.md new file mode 100644 index 0000000000..ed29478675 --- /dev/null +++ b/ports/esp32/boards/UM_TINYPICO/deploy.md @@ -0,0 +1,46 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +Your TinyPICO has an auto-reset circuit on it, so there is no need to put it into a +download mode first to erase or flash it. + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +### Linux +```bash +esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash +``` + +### Mac +```bash +esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash +``` + +### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32 --port COM(X) erase_flash +``` + +Now download the version of the firmware you would like to install from the options below, +then use the following command to program the firmware starting at address 0x1000, +remembering to replace `tinypico-micropython-firmware-version.bin` with the name of the +firmware you just downloaded: + +From then on program the firmware starting at address 0x1000: + +### Linux +```bash +esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 912600 write_flash -z 0x1000 tinypico-micropython-firmware-version.bin +``` + +### Mac +```bash +esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART --baud 912600 write_flash -z 0x1000 tinypico-micropython-firmware-version.bin +``` + +### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32 --port COM(X) --baud 912600 write_flash -z 0x1000 tinypico-micropython-firmware-version.bin +``` diff --git a/ports/esp32/boards/UM_TINYS2/board.json b/ports/esp32/boards/UM_TINYS2/board.json new file mode 100644 index 0000000000..e7068170cb --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/board.json @@ -0,0 +1,26 @@ +{ + "deploy": [ + "deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "RGB LED", + "SPIRAM", + "STEMMA QT/QWIIC", + "USB-C", + "WiFi" + ], + "features_non_filterable": [ + "TinyPICO Compatible" + ], + "id": "tinys2", + "images": [ + "TinyS2+Product+Shot.jpg" + ], + "mcu": "esp32s2", + "product": "Tiny S2", + "thumbnail": "", + "url": "https://unexpectedmaker.com/tinys2", + "vendor": "Unexpected Maker" +} diff --git a/ports/esp32/boards/UM_TINYS2/board.md b/ports/esp32/boards/UM_TINYS2/board.md new file mode 100644 index 0000000000..9a61374edf --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/board.md @@ -0,0 +1,2 @@ +The following files are daily firmware for the TinyS2. This firmware is compiled +using ESP-IDF v4.3 or later. diff --git a/ports/esp32/boards/UM_TINYS2/deploy.md b/ports/esp32/boards/UM_TINYS2/deploy.md new file mode 100644 index 0000000000..a46bc9bd1a --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/deploy.md @@ -0,0 +1,50 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +To flash or erase your TinyS2, you have to first put it into download mode. +To do this, follow these steps: + +- Press and hold the [BOOT] button +- Press and release the [RESET] button +- Release the [BOOT] button + +Now the board is in download mode and the native USB will have enumerated as a serial device. + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +### Linux +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash +``` + +### Mac +```bash +esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 erase_flash +``` + +#### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32s2 --port COM(X) erase_flash +``` + +Now download the version of the firmware you would like to install from the options below, +then use the following command to program the firmware starting at address 0x1000, +remembering to replace `tinys2-micropython-firmware-version.bin` with the name of the +firmware you just downloaded: + +#### Linux +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 tinys2-micropython-firmware-version.bin +``` + +#### Mac +```bash +esptool.py --chip esp32s2 --port /dev/cu.usbmodem01 write_flash -z 0x1000 tinys2-micropython-firmware-version.bin +``` + +#### Windows +Change (X) to whatever COM port is being used by the board +```bash +esptool --chip esp32s2 --port COM(X) write_flash -z 0x1000 tinys2-micropython-firmware-version.bin +``` diff --git a/ports/esp32/boards/deploy.md b/ports/esp32/boards/deploy.md new file mode 100644 index 0000000000..64e683edfc --- /dev/null +++ b/ports/esp32/boards/deploy.md @@ -0,0 +1,14 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash +``` + +From then on program the firmware starting at address 0x1000: + +```bash +esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-20190125-v1.10.bin +``` diff --git a/ports/esp32/boards/deploy_s2.md b/ports/esp32/boards/deploy_s2.md new file mode 100644 index 0000000000..5b3057087d --- /dev/null +++ b/ports/esp32/boards/deploy_s2.md @@ -0,0 +1,14 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash +``` + +From then on program the firmware starting at address 0x1000: + +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 board-20210902-v1.17.bin +``` diff --git a/ports/esp32/boards/manifest.py b/ports/esp32/boards/manifest.py index 6414c1d4e4..d2fc1662ea 100644 --- a/ports/esp32/boards/manifest.py +++ b/ports/esp32/boards/manifest.py @@ -1,7 +1,34 @@ freeze("$(PORT_DIR)/modules") freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) +freeze("$(MPY_DIR)/ports/esp8266/modules", "ntptime.py") freeze("$(MPY_DIR)/drivers/dht", "dht.py") freeze("$(MPY_DIR)/drivers/onewire") include("$(MPY_DIR)/extmod/uasyncio/manifest.py") include("$(MPY_DIR)/extmod/webrepl/manifest.py") include("$(MPY_DIR)/drivers/neopixel/manifest.py") + +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") +freeze("$(MPY_DIR)/drivers/onewire", "onewire.py") + +# upip +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) + +# boardparser +freeze("$(MPY_DIR)/modules/boardparser", "boardparser.py") + +# driver +include("$(MPY_DIR)/modules/driver/manifest.py") + +# modbus +include("$(MPY_DIR)/modules/modbus/manifest.py") + +# ulinksdk +include("$(MPY_DIR)/modules/ulinksdk/manifest.py") + +# http +freeze("$(MPY_DIR)/modules/http", "http.py") + +# ukvfrom +freeze("$(MPY_DIR)/modules/ukv", "kv.py") +# ukv +freeze("$(MPY_DIR)/modules/adaptor/esp32", "systemAdaptor.py") diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 5ca3f6a1a2..71229e8beb 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -3,11 +3,11 @@ CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 -# Compiler options: use -Os to reduce size, but keep full assertions +# Compiler options: use -O2 and disable assertions to improve performance # (CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is for IDF 4.0.2) CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y -CONFIG_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y # Application manager CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y diff --git a/ports/esp32/boards/sdkconfig.spiram b/ports/esp32/boards/sdkconfig.spiram index 12cbef6488..5b4ce118b8 100644 --- a/ports/esp32/boards/sdkconfig.spiram +++ b/ports/esp32/boards/sdkconfig.spiram @@ -4,5 +4,3 @@ CONFIG_ESP32_SPIRAM_SUPPORT=y CONFIG_SPIRAM_CACHE_WORKAROUND=y CONFIG_SPIRAM_IGNORE_NOTFOUND=y CONFIG_SPIRAM_USE_MEMMAP=y -CONFIG_SPIRAM=y -CONFIG_SPIRAM_BOOT_INIT=y diff --git a/ports/esp32/boards/sdkconfig.spiram_sx b/ports/esp32/boards/sdkconfig.spiram_sx index 18a0712cbf..ef24e90829 100644 --- a/ports/esp32/boards/sdkconfig.spiram_sx +++ b/ports/esp32/boards/sdkconfig.spiram_sx @@ -1,5 +1,7 @@ # MicroPython on ESP32-S2 and ESP32-PAD1_subscript_3, ESP IDF configuration with SPIRAM support CONFIG_ESP32S2_SPIRAM_SUPPORT=y +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_MODE_QUAD=y CONFIG_SPIRAM_TYPE_AUTO=y CONFIG_DEFAULT_PSRAM_CLK_IO=30 CONFIG_DEFAULT_PSRAM_CS_IO=26 @@ -9,3 +11,5 @@ CONFIG_SPIRAM_BOOT_INIT=y CONFIG_SPIRAM_IGNORE_NOTFOUND=y CONFIG_SPIRAM_USE_MEMMAP=y CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 diff --git a/ports/esp32/components/dummy_main/CMakeLists.txt b/ports/esp32/components/dummy_main/CMakeLists.txt deleted file mode 100644 index f3a1b1788a..0000000000 --- a/ports/esp32/components/dummy_main/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -idf_component_register(SRC_DIRS "." - ) \ No newline at end of file diff --git a/ports/esp32/components/dummy_main/component.mk b/ports/esp32/components/dummy_main/component.mk deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ports/esp32/components/dummy_main/main_dummy.c b/ports/esp32/components/dummy_main/main_dummy.c deleted file mode 100644 index e2404a1c2c..0000000000 --- a/ports/esp32/components/dummy_main/main_dummy.c +++ /dev/null @@ -1,6 +0,0 @@ -#include - -void app_main(void) -{ - printf(" ====== main_dummy enter ======\r\n"); -} \ No newline at end of file diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 37ecef3248..639e0467a8 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -27,6 +27,9 @@ #include "py/runtime.h" #include "modmachine.h" #include "mphalport.h" +#include "modesp32.h" + +#include "esp_task.h" #include "driver/rmt.h" // This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF: @@ -57,6 +60,38 @@ typedef struct _esp32_rmt_obj_t { bool loop_en; } esp32_rmt_obj_t; +typedef struct _rmt_install_state_t { + SemaphoreHandle_t handle; + uint8_t channel_id; + esp_err_t ret; +} rmt_install_state_t; + +// Current channel used for machine.bitstream, in the machine_bitstream_high_low_rmt +// implementation. A value of -1 means do not use RMT. +int8_t esp32_rmt_bitstream_channel_id = RMT_CHANNEL_MAX - 1; + +STATIC void rmt_install_task(void *pvParameter) { + rmt_install_state_t *state = pvParameter; + state->ret = rmt_driver_install(state->channel_id, 0, 0); + xSemaphoreGive(state->handle); + vTaskDelete(NULL); + for (;;) { + } +} + +// Call rmt_driver_install on core 1. This ensures that the RMT interrupt handler is +// serviced on core 1, so that WiFi (if active) does not interrupt it and cause glitches. +esp_err_t rmt_driver_install_core1(uint8_t channel_id) { + TaskHandle_t th; + rmt_install_state_t state; + state.handle = xSemaphoreCreateBinary(); + state.channel_id = channel_id; + xTaskCreatePinnedToCore(rmt_install_task, "rmt_install_task", 2048 / sizeof(StackType_t), &state, ESP_TASK_PRIO_MIN + 1, &th, 1); + xSemaphoreTake(state.handle, portMAX_DELAY); + vSemaphoreDelete(state.handle); + return state.ret; +} + STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, @@ -73,6 +108,10 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_uint_t idle_level = args[3].u_bool; mp_obj_t tx_carrier_obj = args[4].u_obj; + if (esp32_rmt_bitstream_channel_id >= 0 && channel_id == esp32_rmt_bitstream_channel_id) { + mp_raise_ValueError(MP_ERROR_TEXT("channel used by bitstream")); + } + if (clock_div < 1 || clock_div > 255) { mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); } @@ -119,7 +158,7 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz config.clk_div = self->clock_div; check_esp_err(rmt_config(&config)); - check_esp_err(rmt_driver_install(config.channel, 0, 0)); + check_esp_err(rmt_driver_install_core1(config.channel)); return MP_OBJ_FROM_PTR(self); } @@ -279,6 +318,27 @@ STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_write_pulses_obj, 2, 3, esp32_rmt_write_pulses); +STATIC mp_obj_t esp32_rmt_bitstream_channel(size_t n_args, const mp_obj_t *args) { + if (n_args > 0) { + if (args[0] == mp_const_none) { + esp32_rmt_bitstream_channel_id = -1; + } else { + mp_int_t channel_id = mp_obj_get_int(args[0]); + if (channel_id < 0 || channel_id >= RMT_CHANNEL_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid channel")); + } + esp32_rmt_bitstream_channel_id = channel_id; + } + } + if (esp32_rmt_bitstream_channel_id < 0) { + return mp_const_none; + } else { + return MP_OBJ_NEW_SMALL_INT(esp32_rmt_bitstream_channel_id); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_bitstream_channel_fun_obj, 0, 1, esp32_rmt_bitstream_channel); +STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_bitstream_channel_obj, MP_ROM_PTR(&esp32_rmt_bitstream_channel_fun_obj)); + STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, @@ -287,6 +347,9 @@ STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) }, { MP_ROM_QSTR(MP_QSTR_write_pulses), MP_ROM_PTR(&esp32_rmt_write_pulses_obj) }, + + // Static methods + { MP_ROM_QSTR(MP_QSTR_bitstream_channel), MP_ROM_PTR(&esp32_rmt_bitstream_channel_obj) }, }; STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); diff --git a/ports/esp32/fatfs_port.c b/ports/esp32/fatfs_port.c index 1172c8d20c..b9ad30a128 100644 --- a/ports/esp32/fatfs_port.c +++ b/ports/esp32/fatfs_port.c @@ -30,4 +30,12 @@ #include "lib/oofatfs/ff.h" #include "shared/timeutils/timeutils.h" +DWORD get_fattime(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); + return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | + ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); +} diff --git a/ports/esp32/font_petme128_8x8.h b/ports/esp32/font_petme128_8x8.h deleted file mode 100644 index 9963698b17..0000000000 --- a/ports/esp32/font_petme128_8x8.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2013, 2014 Damien P. George - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef MICROPY_INCLUDED_STM32_FONT_PETME128_8X8_H -#define MICROPY_INCLUDED_STM32_FONT_PETME128_8X8_H - -static const uint8_t font_petme128_8x8[] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 32= - 0x00,0x00,0x00,0x4f,0x4f,0x00,0x00,0x00, // 33=! - 0x00,0x07,0x07,0x00,0x00,0x07,0x07,0x00, // 34=" - 0x14,0x7f,0x7f,0x14,0x14,0x7f,0x7f,0x14, // 35=# - 0x00,0x24,0x2e,0x6b,0x6b,0x3a,0x12,0x00, // 36=$ - 0x00,0x63,0x33,0x18,0x0c,0x66,0x63,0x00, // 37=% - 0x00,0x32,0x7f,0x4d,0x4d,0x77,0x72,0x50, // 38=& - 0x00,0x00,0x00,0x04,0x06,0x03,0x01,0x00, // 39=' - 0x00,0x00,0x1c,0x3e,0x63,0x41,0x00,0x00, // 40=( - 0x00,0x00,0x41,0x63,0x3e,0x1c,0x00,0x00, // 41=) - 0x08,0x2a,0x3e,0x1c,0x1c,0x3e,0x2a,0x08, // 42=* - 0x00,0x08,0x08,0x3e,0x3e,0x08,0x08,0x00, // 43=+ - 0x00,0x00,0x80,0xe0,0x60,0x00,0x00,0x00, // 44=, - 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, // 45=- - 0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x00, // 46=. - 0x00,0x40,0x60,0x30,0x18,0x0c,0x06,0x02, // 47=/ - 0x00,0x3e,0x7f,0x49,0x45,0x7f,0x3e,0x00, // 48=0 - 0x00,0x40,0x44,0x7f,0x7f,0x40,0x40,0x00, // 49=1 - 0x00,0x62,0x73,0x51,0x49,0x4f,0x46,0x00, // 50=2 - 0x00,0x22,0x63,0x49,0x49,0x7f,0x36,0x00, // 51=3 - 0x00,0x18,0x18,0x14,0x16,0x7f,0x7f,0x10, // 52=4 - 0x00,0x27,0x67,0x45,0x45,0x7d,0x39,0x00, // 53=5 - 0x00,0x3e,0x7f,0x49,0x49,0x7b,0x32,0x00, // 54=6 - 0x00,0x03,0x03,0x79,0x7d,0x07,0x03,0x00, // 55=7 - 0x00,0x36,0x7f,0x49,0x49,0x7f,0x36,0x00, // 56=8 - 0x00,0x26,0x6f,0x49,0x49,0x7f,0x3e,0x00, // 57=9 - 0x00,0x00,0x00,0x24,0x24,0x00,0x00,0x00, // 58=: - 0x00,0x00,0x80,0xe4,0x64,0x00,0x00,0x00, // 59=; - 0x00,0x08,0x1c,0x36,0x63,0x41,0x41,0x00, // 60=< - 0x00,0x14,0x14,0x14,0x14,0x14,0x14,0x00, // 61== - 0x00,0x41,0x41,0x63,0x36,0x1c,0x08,0x00, // 62=> - 0x00,0x02,0x03,0x51,0x59,0x0f,0x06,0x00, // 63=? - 0x00,0x3e,0x7f,0x41,0x4d,0x4f,0x2e,0x00, // 64=@ - 0x00,0x7c,0x7e,0x0b,0x0b,0x7e,0x7c,0x00, // 65=A - 0x00,0x7f,0x7f,0x49,0x49,0x7f,0x36,0x00, // 66=B - 0x00,0x3e,0x7f,0x41,0x41,0x63,0x22,0x00, // 67=C - 0x00,0x7f,0x7f,0x41,0x63,0x3e,0x1c,0x00, // 68=D - 0x00,0x7f,0x7f,0x49,0x49,0x41,0x41,0x00, // 69=E - 0x00,0x7f,0x7f,0x09,0x09,0x01,0x01,0x00, // 70=F - 0x00,0x3e,0x7f,0x41,0x49,0x7b,0x3a,0x00, // 71=G - 0x00,0x7f,0x7f,0x08,0x08,0x7f,0x7f,0x00, // 72=H - 0x00,0x00,0x41,0x7f,0x7f,0x41,0x00,0x00, // 73=I - 0x00,0x20,0x60,0x41,0x7f,0x3f,0x01,0x00, // 74=J - 0x00,0x7f,0x7f,0x1c,0x36,0x63,0x41,0x00, // 75=K - 0x00,0x7f,0x7f,0x40,0x40,0x40,0x40,0x00, // 76=L - 0x00,0x7f,0x7f,0x06,0x0c,0x06,0x7f,0x7f, // 77=M - 0x00,0x7f,0x7f,0x0e,0x1c,0x7f,0x7f,0x00, // 78=N - 0x00,0x3e,0x7f,0x41,0x41,0x7f,0x3e,0x00, // 79=O - 0x00,0x7f,0x7f,0x09,0x09,0x0f,0x06,0x00, // 80=P - 0x00,0x1e,0x3f,0x21,0x61,0x7f,0x5e,0x00, // 81=Q - 0x00,0x7f,0x7f,0x19,0x39,0x6f,0x46,0x00, // 82=R - 0x00,0x26,0x6f,0x49,0x49,0x7b,0x32,0x00, // 83=S - 0x00,0x01,0x01,0x7f,0x7f,0x01,0x01,0x00, // 84=T - 0x00,0x3f,0x7f,0x40,0x40,0x7f,0x3f,0x00, // 85=U - 0x00,0x1f,0x3f,0x60,0x60,0x3f,0x1f,0x00, // 86=V - 0x00,0x7f,0x7f,0x30,0x18,0x30,0x7f,0x7f, // 87=W - 0x00,0x63,0x77,0x1c,0x1c,0x77,0x63,0x00, // 88=X - 0x00,0x07,0x0f,0x78,0x78,0x0f,0x07,0x00, // 89=Y - 0x00,0x61,0x71,0x59,0x4d,0x47,0x43,0x00, // 90=Z - 0x00,0x00,0x7f,0x7f,0x41,0x41,0x00,0x00, // 91=[ - 0x00,0x02,0x06,0x0c,0x18,0x30,0x60,0x40, // 92='\' - 0x00,0x00,0x41,0x41,0x7f,0x7f,0x00,0x00, // 93=] - 0x00,0x08,0x0c,0x06,0x06,0x0c,0x08,0x00, // 94=^ - 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0, // 95=_ - 0x00,0x00,0x01,0x03,0x06,0x04,0x00,0x00, // 96=` - 0x00,0x20,0x74,0x54,0x54,0x7c,0x78,0x00, // 97=a - 0x00,0x7f,0x7f,0x44,0x44,0x7c,0x38,0x00, // 98=b - 0x00,0x38,0x7c,0x44,0x44,0x6c,0x28,0x00, // 99=c - 0x00,0x38,0x7c,0x44,0x44,0x7f,0x7f,0x00, // 100=d - 0x00,0x38,0x7c,0x54,0x54,0x5c,0x58,0x00, // 101=e - 0x00,0x08,0x7e,0x7f,0x09,0x03,0x02,0x00, // 102=f - 0x00,0x98,0xbc,0xa4,0xa4,0xfc,0x7c,0x00, // 103=g - 0x00,0x7f,0x7f,0x04,0x04,0x7c,0x78,0x00, // 104=h - 0x00,0x00,0x00,0x7d,0x7d,0x00,0x00,0x00, // 105=i - 0x00,0x40,0xc0,0x80,0x80,0xfd,0x7d,0x00, // 106=j - 0x00,0x7f,0x7f,0x30,0x38,0x6c,0x44,0x00, // 107=k - 0x00,0x00,0x41,0x7f,0x7f,0x40,0x00,0x00, // 108=l - 0x00,0x7c,0x7c,0x18,0x30,0x18,0x7c,0x7c, // 109=m - 0x00,0x7c,0x7c,0x04,0x04,0x7c,0x78,0x00, // 110=n - 0x00,0x38,0x7c,0x44,0x44,0x7c,0x38,0x00, // 111=o - 0x00,0xfc,0xfc,0x24,0x24,0x3c,0x18,0x00, // 112=p - 0x00,0x18,0x3c,0x24,0x24,0xfc,0xfc,0x00, // 113=q - 0x00,0x7c,0x7c,0x04,0x04,0x0c,0x08,0x00, // 114=r - 0x00,0x48,0x5c,0x54,0x54,0x74,0x20,0x00, // 115=s - 0x04,0x04,0x3f,0x7f,0x44,0x64,0x20,0x00, // 116=t - 0x00,0x3c,0x7c,0x40,0x40,0x7c,0x3c,0x00, // 117=u - 0x00,0x1c,0x3c,0x60,0x60,0x3c,0x1c,0x00, // 118=v - 0x00,0x1c,0x7c,0x30,0x18,0x30,0x7c,0x1c, // 119=w - 0x00,0x44,0x6c,0x38,0x38,0x6c,0x44,0x00, // 120=x - 0x00,0x9c,0xbc,0xa0,0xa0,0xfc,0x7c,0x00, // 121=y - 0x00,0x44,0x64,0x74,0x5c,0x4c,0x44,0x00, // 122=z - 0x00,0x08,0x08,0x3e,0x77,0x41,0x41,0x00, // 123={ - 0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00, // 124=| - 0x00,0x41,0x41,0x77,0x3e,0x08,0x08,0x00, // 125=} - 0x00,0x02,0x03,0x01,0x03,0x02,0x03,0x01, // 126=~ - 0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55, // 127 -}; - -#endif // MICROPY_INCLUDED_STM32_FONT_PETME128_8X8_H diff --git a/ports/esp32/fs/boot.py b/ports/esp32/fs/boot.py deleted file mode 100644 index 03ca752813..0000000000 --- a/ports/esp32/fs/boot.py +++ /dev/null @@ -1,40 +0,0 @@ -import kv -import time -import _thread - -def _on_get_url(url): - kv.set('_amp_pyapp_url',url) - execfile('/lib/appOta.py') - -def _connect_wifi(): - ssid = kv.get('_amp_wifi_ssid') - passwd = kv.get('_amp_wifi_passwd') - if isinstance(ssid,str) and isinstance(passwd,str): - import network - sta_if = network.WLAN(network.STA_IF) - if not sta_if.isconnected(): - sta_if.active(True) - sta_if.scan() - sta_if.connect(ssid,passwd) - -channel = kv.get('app_upgrade_channel') -if channel == "disable": - pass -elif channel == "bt": - execfile('/lib/oneMinuteOnCloud.py') -else: - import online_upgrade - online_upgrade.on(_on_get_url) - try: - _thread.start_new_thread(_connect_wifi, ()) - #_thread.stack_size(10 * 1024) - except Exception as e: - print(e) - - - - - - - - diff --git a/ports/esp32/fs/lib/appOta.py b/ports/esp32/fs/lib/appOta.py deleted file mode 100644 index 0953c2257d..0000000000 --- a/ports/esp32/fs/lib/appOta.py +++ /dev/null @@ -1,150 +0,0 @@ -import uos as os -import uerrno as errno -import ujson as json -import uzlib -import upip_utarfile as tarfile -import gc -import time -import ussl -import usocket -import kv -import machine - -file_buf = bytearray(128) - - -def install_pkg(package_url, install_path): - gzdict_sz = 16 + 15 - f1 = url_open(package_url) - if (isinstance(f1, (str, bytes, bytearray)) == True): - return f1 - try: - f2 = uzlib.DecompIO(f1, gzdict_sz) - f3 = tarfile.TarFile(fileobj=f2) - install_tar(f3, install_path) - except Exception as e: - print(e) - return ("UNTAR_FILE_FAIL") - finally: - f1.close() - del f3 - del f2 - gc.collect() - - return 'SUCCESS' - - -def download_save_file(file_url, fname): - global file_buf - f1 = url_open(file_url) - - if (isinstance(f1, (str, bytes, bytearray)) == True): - return f1 - - _makedirs(fname) - with open(fname, "wb") as outf: - while True: - sz = f1.readinto(file_buf) - if not sz: - break - outf.write(file_buf, sz) - outf.close() - f1.close() - del f1 - return 'SUCCESS' - - -def url_open(url): - proto, _, host, urlpath = url.split('/', 3) - try: - port = 443 - if ":" in host: - host, port = host.split(":") - port = int(port) - ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM) - except OSError as e: - print("Error:", "Unable to resolve %s (no Internet?)" % host, e) - return 'HOST_RESOLVED_FAIL' - ai = ai[0] - s = usocket.socket(ai[0], ai[1], ai[2]) - try: - s.connect(ai[-1]) - if proto == "https:": - s = ussl.wrap_socket(s, server_hostname=host) - s.write("GET /%s HTTP/1.0\r\nHost: %s:%s\r\n\r\n" % - (urlpath, host, port)) - l = s.readline() - protover, status, msg = l.split(None, 2) - if status != b"200": - if status == b"404" or status == b"301": - return ("Package not found") - else: - print("status is {}".format(status)) - return (status) - while True: - l = s.readline() - if not l: - return ("Unexpected EOF in HTTP headers") - if l == b'\r\n': - break - except Exception as e: - s.close() - print(e) - return ('SOCKET_ERROR') - return s - - -def _makedirs(name, mode=0o777): - ret = False - s = "" - comps = name.rstrip("/").split("/")[:-1] - if comps[0] == "": - s = "/" - for c in comps: - if s and s[-1] != "/": - s += "/" - s += c - try: - os.mkdir(s) - ret = True - except OSError as e: - if e.args[0] != errno.EEXIST and e.args[0] != errno.EISDIR: - print(e) - ret = False - return ret - - -def install_tar(f, prefix): - for info in f: - fname = info.name - #try: - #fname = fname[fname.index("/") + 1:] - #except ValueError: - #fname = "" - outfname = prefix + fname - if info.type != tarfile.DIRTYPE: - _makedirs(outfname) - subf = f.extractfile(info) - save_file(outfname, subf) - - -def save_file(fname, subf): - global file_buf - with open(fname, "wb") as outf: - while True: - sz = subf.readinto(file_buf) - if not sz: - break - outf.write(file_buf, sz) - outf.close() - - -#download_save_file(url,"/data/pyamp/main.py") - -if __name__ == "__main__": - - url = kv.get('_amp_pyapp_url') - if isinstance(url, str): - install_pkg(url, "/data/pyamp/") - kv.remove('_amp_pyapp_url') - machine.reset() diff --git a/ports/esp32/fs/lib/bleNetConfig.py b/ports/esp32/fs/lib/bleNetConfig.py deleted file mode 100644 index 5ac2581db4..0000000000 --- a/ports/esp32/fs/lib/bleNetConfig.py +++ /dev/null @@ -1,130 +0,0 @@ -import ubluetooth -import json -import gc -import time -import network -from micropython import const - -_wlan = network.WLAN(network.STA_IF) - -_ble = ubluetooth.BLE() -_bleNetConfigStatus = None -_ble_adv_name = 'esp-node' -_ble_tx = None -_ble_rx = None -_ble_msg = '' - -BLE_CONNECTED = const(0x00) -BLE_DISCONNECTED = const(0x01) -BLE_COMMINICATING = const(0x02) - -WIFI_IDLE = 1000 -WIFI_CONNECTING = 1001 -WIFI_GOT_IP = network.STAT_GOT_IP - -NUS_UUID = 0xFFA0 -RX_UUID = 0xFFA2 -TX_UUID = 0xFFA3 - -BLE_NUS = ubluetooth.UUID(NUS_UUID) -BLE_RX = (ubluetooth.UUID(RX_UUID), ubluetooth.FLAG_WRITE) -BLE_TX = (ubluetooth.UUID(TX_UUID), ubluetooth.FLAG_NOTIFY | ubluetooth.FLAG_READ) - -BLE_UART = (BLE_NUS, (BLE_TX, BLE_RX,)) -SERVICES = [BLE_UART, ] - -def send(data): - _ble.gatts_notify(0, _ble_tx, data + '\n') - -def advertiser(name): - _name = bytes(name, 'UTF-8') - _ble.gap_advertise(100, bytearray('\x02\x01\x02') + bytearray((len(_name) + 1, 0x09)) + _name) - -def ble_irq(event, data): - global _ble_msg, _bleNetConfigStatus - - if event == 1: - _bleNetConfigStatus = BLE_CONNECTED - - elif event == 2: - _bleNetConfigStatus = BLE_DISCONNECTED - advertiser("esp-node") - - elif event == 3: - buffer = _ble.gatts_read(_ble_rx) - _ble_msg += buffer.decode('hex').strip() - _ble_msg = '{"cmd":' + _ble_msg.split('{"cmd":')[-1] - if(_ble_msg.count('{') == _ble_msg.count('}')): - try: - cmdd = json.loads(_ble_msg) - except Exception as e: - pass - else: - if(cmdd['cmd'] == 'WiFiCon'): - _wlan.active(True) - if(_wlan.isconnected()): - _wlan.disconnect() - - _wlan.connect(cmdd['param']['ssid'], cmdd['param']['pswd']) - timeout = 5 - if('timeout' in cmdd['param'].keys()): - timeout = int(cmdd['param']['timeout']) - while(True): - status = _wlan.status() - if(status == network.STAT_WRONG_PASSWORD): - _bleNetConfigStatus = BLE_COMMINICATING - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_WRONG_PASSWORD'}} - send(json.dumps(ret).encode('hex')) - _bleNetConfigStatus = BLE_CONNECTED - break - if(status == network.STAT_NO_AP_FOUND): - _bleNetConfigStatus = BLE_COMMINICATING - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_NO_AP_FOUND'}} - send(json.dumps(ret).encode('hex')) - _bleNetConfigStatus = BLE_CONNECTED - break - if(status == network.STAT_GOT_IP): - _bleNetConfigStatus = BLE_COMMINICATING - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_GOT_IP', 'ifconfig':_wlan.ifconfig()}} - send(json.dumps(ret).encode('hex')) - _bleNetConfigStatus = BLE_CONNECTED - break - if(status == 1001): - pass - if(timeout < 0): - _bleNetConfigStatus = BLE_COMMINICATING - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_CONNECT_TIMEOUT'}} - send(json.dumps(ret).encode('hex')) - _bleNetConfigStatus = BLE_CONNECTED - break - time.sleep(1) - timeout -= 1 - _ble_msg = '' - gc.collect() - - -def start(): - global _ble,_ble_tx, _ble_rx, _bleNetConfigStatus - _ble.active(True) - ((_ble_tx, _ble_rx,), ) = _ble.gatts_register_services(SERVICES) - _ble.irq(ble_irq) - advertiser(_ble_adv_name) - _bleNetConfigStatus = BLE_DISCONNECTED - -def stop(): - global _ble,_bleNetConfigStatus - _ble.irq(None) - _ble.active(False) - _bleNetConfigStatus = BLE_DISCONNECTED - -def getWLAN(): - return _wlan - -def getBleStatus(): - return _bleNetConfigStatus - -def getWiFiStatus(): - return _wlan.status() - -def getWiFiConfig(): - return _wlan.ifconfig() diff --git a/ports/esp32/fs/lib/display_driver.py b/ports/esp32/fs/lib/display_driver.py deleted file mode 100644 index cae548b4c7..0000000000 --- a/ports/esp32/fs/lib/display_driver.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Copyright (C) 2015-2021 Alibaba Group Holding Limited - - MicroPython's driver for CHT8305 - - Author: HaaS - Date: 2022/05/11 -""" - -import lvgl as lv -import lvgl_display - -print("display_driver init") -if not lv.is_initialized(): - #print("lv.init") - lv.init() - -if not lvgl_display.is_initialized(): - #print("lvgl_display.init") - lvgl_display.init() diff --git a/ports/esp32/fs/lib/oneMinuteOnCloud.py b/ports/esp32/fs/lib/oneMinuteOnCloud.py deleted file mode 100644 index 53bc05e726..0000000000 --- a/ports/esp32/fs/lib/oneMinuteOnCloud.py +++ /dev/null @@ -1,347 +0,0 @@ -print('enable OneMinuteOnCloud') -import ubluetooth -import uos as os -import uerrno as errno -import ujson as json -import uzlib -import upip_utarfile as tarfile -import gc -import time -import machine -import ussl -import usocket -import network - -_wlan = network.WLAN(network.STA_IF) -_ble = ubluetooth.BLE() - -_ble_adv_name = 'esp-node' - -pull_code_state = [] -file_buf = bytearray(128) - -def download_file_task(filelist): - global pull_code_state - for file_task in filelist: - if('needunpack' in file_task.keys() and file_task['needunpack'] == True): - gc.collect() - print(gc.mem_free()) - pull_code_state.append(install_pkg(file_task['url'], file_task['path'])) - gc.collect() - else: - gc.collect() - print(gc.mem_free()) - pull_code_state.append(download_save_file(file_task['url'], file_task['path']+file_task['name'])) - gc.collect() - -def download_save_file(file_url, fname): - global file_buf - f1 = url_open(file_url) - if(isinstance(f1, (str, bytes, bytearray)) == True): - print(f1) - return f1 - - print(fname) - _makedirs(fname) - with open(fname, "wb") as outf: - while True: - sz = f1.readinto(file_buf) - if not sz: - break - outf.write(file_buf, sz) - outf.close() - f1.close() - del f1 - print('download_save_file success') - return 'SUCCESS' - -def install_pkg(package_url, install_path): - gzdict_sz = 16 + 15 - f1 = url_open(package_url) - if(isinstance(f1, (str, bytes, bytearray)) == True): - print(f1) - return f1 - try: - f2 = uzlib.DecompIO(f1, gzdict_sz) - f3 = tarfile.TarFile(fileobj=f2) - install_tar(f3, install_path) - except Exception as e: - print(e) - return("UNTAR_FILE_FAIL") - finally: - f1.close() - del f3 - del f2 - gc.collect() - - print('install_pkg success') - return 'SUCCESS' - -def url_open(url): - proto, _, host, urlpath = url.split('/', 3) - try: - port = 443 - if ":" in host: - host, port = host.split(":") - port = int(port) - ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM) - except OSError as e: - print("Error:", "Unable to resolve %s (no Internet?)" % host, e) - return 'HOST_RESOLVED_FAIL' - print("Address infos:", ai) - ai = ai[0] - s = usocket.socket(ai[0], ai[1], ai[2]) - try: - s.connect(ai[-1]) - if proto == "https:": - s = ussl.wrap_socket(s, server_hostname=host) - s.write("GET /%s HTTP/1.0\r\nHost: %s:%s\r\n\r\n" % (urlpath, host, port)) - l = s.readline() - protover, status, msg = l.split(None, 2) - if status != b"200": - if status == b"404" or status == b"301": - return("Package not found") - return(status) - while True: - l = s.readline() - if not l: - return("Unexpected EOF in HTTP headers") - if l == b'\r\n': - break - except Exception as e: - s.close() - print(e) - return('SOCKET_ERROR') - return s - -def save_file(fname, subf): - global file_buf - with open(fname, "wb") as outf: - while True: - sz = subf.readinto(file_buf) - if not sz: - break - outf.write(file_buf, sz) - outf.close() - -def _makedirs(name, mode=0o777): - ret = False - s = "" - comps = name.rstrip("/").split("/")[:-1] - if comps[0] == "": - s = "/" - for c in comps: - if s and s[-1] != "/": - s += "/" - s += c - try: - os.mkdir(s) - ret = True - except OSError as e: - if e.args[0] != errno.EEXIST and e.args[0] != errno.EISDIR: - print(e) - ret = False - return ret - -def install_tar(f, prefix): - for info in f: - print(info) - fname = info.name - try: - fname = fname[fname.index("/") + 1:] - except ValueError: - fname = "" - - outfname = prefix + fname - if info.type != tarfile.DIRTYPE: - print("Extracting " + outfname) - _makedirs(outfname) - subf = f.extractfile(info) - save_file(outfname, subf) - -def rmvdir(dir): - for i in os.ilistdir(dir): - if i[1] == 16384: - rmvdir('{}/{}'.format(dir,i)) - elif i[1] == 32678: - os.remove('{}/{}'.format(dir,i[0])) - os.rmdir(dir) - -def send(data): - _ble.gatts_notify(0, _ble_tx, data + '\n') - -def advertiser(name): - _name = bytes(name, 'UTF-8') - _ble.gap_advertise(100, bytearray('\x02\x01\x02') + bytearray((len(_name) + 1, 0x09)) + _name) - -def ble_irq(event, data): - global ble_msg - - if event == 1: - print('Central connected') - - global pull_code_state - if(pull_code_state!=[]): - ret = {'cmd':'PullCode', 'ret':{'state':pull_code_state}} - send(json.dumps(ret).encode('hex')) - - elif event == 2: - print('Central disconnected') - advertiser("esp-node") - - elif event == 3: - buffer = _ble.gatts_read(_ble_rx) - ble_msg += buffer.decode('hex').strip() - - ble_msg = '{"cmd":' + ble_msg.split('{"cmd":')[-1] - # only save one cmd - print(ble_msg) - - if(ble_msg.count('{') == ble_msg.count('}')): - try: - cmdd = json.loads(ble_msg) - print(cmdd) - - if(cmdd['cmd'] == 'WiFiCon'): - _wlan.active(True) - if(_wlan.isconnected()): - _wlan.disconnect() - print(cmdd['param']['ssid'], cmdd['param']['pswd']) - _wlan.connect(cmdd['param']['ssid'], cmdd['param']['pswd']) - timeout = 5 - if('timeout' in cmdd['param'].keys()): - timeout = int(cmdd['param']['timeout']) - while(True): - status = _wlan.status() - print(status) - if(status == network.STAT_WRONG_PASSWORD): - print('STAT_WRONG_PASSWORD') - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_WRONG_PASSWORD'}} - send(json.dumps(ret).encode('hex')) - break - if(status == network.STAT_NO_AP_FOUND): - print('STAT_NO_AP_FOUND') - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_NO_AP_FOUND'}} - send(json.dumps(ret).encode('hex')) - break - if(status == network.STAT_GOT_IP): - print('STAT_GOT_IP') - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_GOT_IP', 'ifconfig':_wlan.ifconfig()}} - send(json.dumps(ret).encode('hex')) - - wificonf = {"ssid":cmdd['param']['ssid'],"pswd":cmdd['param']['pswd'],"autoConnect":True} - with open('/WiFi.json', "w") as f: - f.write(json.dumps(wificonf) + "\n") - break - if(status == 1001): - print('scaning for ap ...') - if(timeout < 0): - print('STAT_CONNECT_TIMEOUT') - ret = {'cmd':'WiFiCon', 'ret':{'state':'STAT_CONNECT_TIMEOUT'}} - send(json.dumps(ret).encode('hex')) - break - time.sleep(1) - timeout -= 1 - - if(cmdd['cmd'] == 'PullCode'): - global pull_code_state - - if('main.py' in os.listdir('/data/pyamp')): - os.remove('/data/pyamp/main.py') - - if(_wlan.isconnected() is False): - print(_wlan.isconnected()) - ret = {'cmd':'PullCode', 'ret':{'state':'NO_NETWORK'}} - send(json.dumps(ret).encode('hex')) - else: - # _thread.start_new_thread(download_file_task, (cmdd['param']['filelist'], )) - try: - f = open('/afterlife.json', "w") - f.write(json.dumps(cmdd) + "\n") - f.close() - except Exception as e: - print(e) - pass - else: - # see you afterlife - ret = {'cmd':'PullCode', 'ret':{'state':'START_DOWNLOAD'}} - send(json.dumps(ret).encode('hex')) - - if(cmdd['cmd'] == 'DeviceInfo'): - with open('/DeviceInfo.json', "w") as f: - f.write(cmdd['param'] + "\n") - ret = {'cmd':'DeviceInfo', 'ret':{'state':'DeviceInfoRecved'}} - send(json.dumps(ret).encode('hex')) - - if(cmdd['cmd'] == 'PullCodeCheck'): - ret = {'cmd':'PullCode', 'ret':{'state':pull_code_state}} - send(json.dumps(ret).encode('hex')) - - if(cmdd['cmd'] == 'Reset'): - machine.reset() - - ble_msg = '' - gc.collect() - - except Exception as e: - pass - -if('WiFi.json' in os.listdir('/')): - try: - f = open('/WiFi.json', "r") - wificonf = f.readline() - wificonf = json.loads(wificonf) - f.close() - if('autoConnect' in wificonf.keys() and wificonf['autoConnect'] == True): - print('autoConnect') - _wlan.active(True) - _wlan.connect(wificonf['ssid'], wificonf['pswd'],) - if('main.py' in os.listdir('/data/pyamp')): - os.remove('/WiFi.json') - except Exception as e: - print('try WiFi autoConnect, found') - print(e) - pass - -if('afterlife.json' in os.listdir('/')): - try: - f = open('/afterlife.json', "r") - wish = f.readline() - wish = json.loads(wish) - f.close() - print(wish) - time.sleep(5) - if(_wlan.isconnected() == False): - pull_code_state = 'NO_NETWORK' - print('NO_NETWORK') - raise - print('wifi connected') - if('cmd' in wish.keys() and wish['cmd'] == 'PullCode'): - download_file_task(wish['param']['filelist']) - except Exception as e: - raise (e) - -_ble.active(True) -NUS_UUID = 0xFFA0 -RX_UUID = 0xFFA2 -TX_UUID = 0xFFA3 - -BLE_NUS = ubluetooth.UUID(NUS_UUID) -BLE_RX = (ubluetooth.UUID(RX_UUID), ubluetooth.FLAG_WRITE) -BLE_TX = (ubluetooth.UUID(TX_UUID), ubluetooth.FLAG_NOTIFY | ubluetooth.FLAG_READ) -BLE_UART = (BLE_NUS, (BLE_TX, BLE_RX,)) -SERVICES = [BLE_UART, ] - -_ble_tx = None -_ble_rx = None - -ble_msg = '' - -((_ble_tx, _ble_rx,), ) = _ble.gatts_register_services(SERVICES) - -_ble.irq(ble_irq) -advertiser(_ble_adv_name) - -if('afterlife.json' in os.listdir('/')): - os.remove('/afterlife.json') - time.sleep(10) diff --git a/ports/esp32/libs/neopixel.c b/ports/esp32/libs/neopixel.c deleted file mode 100644 index 70f5d03793..0000000000 --- a/ports/esp32/libs/neopixel.c +++ /dev/null @@ -1,570 +0,0 @@ -/* - * This file is part of the MicroPython ESP32 project, https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo - * - * The MIT License (MIT) - * - * Copyright (c) 2018 LoBo (https://github.com/loboris) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include -#include - -#include "driver/gpio.h" -#include "driver/rmt.h" -#include "soc/dport_reg.h" -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "libs/neopixel.h" -#include "esp_log.h" - -static xSemaphoreHandle neopixel_sem = NULL; -static intr_handle_t rmt_intr_handle = NULL; -static rmt_channel_t RMTchannel = RMT_CHANNEL_0; -static uint16_t neopixel_pos, neopixel_half, neopixel_bufIsDirty, neopixel_termsent; -static uint16_t neopixel_buf_len = 0; -static pixel_settings_t *neopixel_px; -static uint8_t *neopixel_buffer = NULL; -static uint8_t neopixel_brightness = 255; - -static uint8_t used_channels[RMT_CHANNEL_MAX] = {0}; - -// Get color value of RGB component -//--------------------------------------------------- -static uint8_t offset_color(char o, uint32_t color) { - uint8_t clr = 0; - switch(o) { - case 'R': - clr = (uint8_t)(color >> 24); - break; - case 'G': - clr = (uint8_t)(color >> 16); - break; - case 'B': - clr = (uint8_t)(color >> 8); - break; - case 'W': - clr = (uint8_t)(color & 0xFF); - break; - default: - clr = 0; - } - return clr; -} - -// Set pixel color at buffer position from RGB color value -//========================================================================= -void np_set_pixel_color(pixel_settings_t *px, uint16_t idx, uint32_t color) -{ - uint16_t ofs = idx * (px->nbits / 8); - px->pixels[ofs] = offset_color(px->color_order[0], color); - px->pixels[ofs+1] = offset_color(px->color_order[1], color); - px->pixels[ofs+2] = offset_color(px->color_order[2], color); - if (px->nbits == 32) px->pixels[ofs+3] = offset_color(px->color_order[3], color); -} - -// Set pixel color at buffer position from HSB color value -//============================================================================================================ -void np_set_pixel_color_hsb(pixel_settings_t *px, uint16_t idx, float hue, float saturation, float brightness) -{ - uint32_t color = hsb_to_rgb(hue, saturation, brightness); - np_set_pixel_color(px, idx, color); -} - -// Get RGB color value from RGB components corrected by brightness factor -//============================================================================= -uint32_t np_get_pixel_color(pixel_settings_t *px, uint16_t idx, uint8_t *white) -{ - uint32_t clr = 0; - uint32_t color = 0; - uint8_t bpp = px->nbits/8; - uint16_t ofs = idx * bpp; - - for (int i=0; i < bpp; i++) { - clr = (uint16_t)px->pixels[ofs+i]; - switch(px->color_order[i]) { - case 'R': - color |= (uint32_t)clr << 16; - break; - case 'G': - color |= (uint32_t)clr << 8; - break; - case 'B': - color |= (uint32_t)clr; - break; - case 'W': - *white = px->pixels[ofs+i]; - break; - } - } - return color; -} - -// Set two levels of RMT output to the Neopixel value for bit value "1". -//-------------------------------------------------------------------- -static void neopixel_mark(rmt_item32_t *pItem, pixel_settings_t *px) { - pItem->level0 = px->timings.mark.level0; - pItem->duration0 = px->timings.mark.duration0; - pItem->level1 = px->timings.mark.level1; - pItem->duration1 = px->timings.mark.duration1; -} - -// Set two levels of RMT output to the Neopixel value for bit value "0". -//--------------------------------------------------------------------- -static void neopixel_space(rmt_item32_t *pItem, pixel_settings_t *px) { - pItem->level0 = px->timings.space.level0; - pItem->duration0 = px->timings.space.duration0; - pItem->level1 = px->timings.space.level1; - pItem->duration1 = px->timings.space.duration1; -} - -// Set levels and duration of RMT output to the Neopixel value for Reset. -//-------------------------------------------------------------------- -static void rmt_terminate(rmt_item32_t *pItem, pixel_settings_t *px) { - pItem->level0 = px->timings.reset.level0; - pItem->duration0 = px->timings.reset.duration0; - pItem->level1 = px->timings.reset.level1; - pItem->duration1 = px->timings.reset.duration1; -} - -// Transfer pixels from buffer to Neopixel strip -//------------------------------- -static void copyToRmtBlock_half() -{ - // This fills half an RMT block - // When wrap around is happening, we want to keep the inactive half of the RMT block filled - uint16_t i, offset, len, byteval; - rmt_item32_t CurrentItem; - offset = neopixel_half * MAX_PULSES; - neopixel_half = !neopixel_half; // for next offset calculation - int j; - - len = neopixel_buf_len - neopixel_pos; // remaining bytes in buffer - if (len > (MAX_PULSES / 8)) len = (MAX_PULSES / 8); - - if (!len) { - if (!neopixel_bufIsDirty) return; - // Clear the channel's data block and return - j = 0; - if (!neopixel_termsent) { - i++; - rmt_terminate(&CurrentItem, neopixel_px); - RMTMEM.chan[RMTchannel].data32[0].val = CurrentItem.val; - neopixel_termsent = 1; - j++; - } - for (i = j; i < MAX_PULSES; i++) { - RMTMEM.chan[RMTchannel].data32[i + offset].val = 0; - } - neopixel_bufIsDirty = 0; - return; - } - neopixel_bufIsDirty = 1; - - // Populate RMT bit buffer from 'neopixel_buffer' containing one byte for each RGB(W) value - for (i = 0; i < len; i++) { - byteval = (uint16_t)neopixel_buffer[i+neopixel_pos]; - // Correct by brightness factor - byteval = (byteval * neopixel_brightness) / 255; - - // Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to the rmtPulsePair value corresponding to the buffered bit value - for (j=7; j>=0; j--) { - if (byteval & (1<pixel_count * (px->nbits / 8); - - // Allocate neopixel buffer if needed - if (neopixel_buffer == NULL) { - neopixel_buffer = (uint8_t *)malloc(blen); - if (neopixel_buffer == NULL) return; - neopixel_buf_len = blen; - } - // Resize neopixel buffer if needed - if (neopixel_buf_len < blen) { - // larger buffer needed - free(neopixel_buffer); - neopixel_buffer = (uint8_t *)malloc(blen); - if (neopixel_buffer == NULL) return; - } - memcpy(neopixel_buffer, px->pixels, blen); - - neopixel_buf_len = blen; - neopixel_pos = 0; - neopixel_half = 0; - neopixel_px = px; - neopixel_half = 0; - neopixel_termsent = 0; - neopixel_brightness = px->brightness; - - copyToRmtBlock_half(); - - if (neopixel_pos < neopixel_buf_len) { - // Fill the other half of the buffer block - copyToRmtBlock_half(); - } - - // Start sending - RMT.conf_ch[RMTchannel].conf1.mem_rd_rst = 1; - RMT.conf_ch[RMTchannel].conf1.tx_start = 1; - - // Wait for operation to finish - if (xSemaphoreTake(neopixel_sem, 0) == pdTRUE) { - xSemaphoreTake(neopixel_sem, portMAX_DELAY); - } - xSemaphoreGive(neopixel_sem); -} - -// Clear the Neopixel color buffer -//================================= -void np_clear(pixel_settings_t *px) -{ - memset(px->pixels, 0, px->pixel_count * (px->nbits/8)); -} - -//------------------------------------ -static float Min(double a, double b) { - return a <= b ? a : b; -} - -//------------------------------------ -static float Max(double a, double b) { - return a >= b ? a : b; -} - -// Convert 24-bit color to HSB representation -//=================================================================== -void rgb_to_hsb( uint32_t color, float *hue, float *sat, float *bri ) -{ - float delta, min; - float h = 0, s, v; - uint8_t red = (color >> 16) & 0xFF; - uint8_t green = (color >> 8) & 0xFF; - uint8_t blue = color & 0xFF; - - min = Min(Min(red, green), blue); - v = Max(Max(red, green), blue); - delta = v - min; - - if (v == 0.0) s = 0; - else s = delta / v; - - if (s == 0) h = 0.0; - else - { - if (red == v) - h = (green - blue) / delta; - else if (green == v) - h = 2 + (blue - red) / delta; - else if (blue == v) - h = 4 + (red - green) / delta; - - h *= 60; - - if (h < 0.0) h = h + 360; - } - - *hue = h; - *sat = s; - *bri = v / 255; -} - -// Convert HSB color to 24-bit color representation -//============================================================ -uint32_t hsb_to_rgb(float _hue, float _sat, float _brightness) -{ - float red = 0.0; - float green = 0.0; - float blue = 0.0; - - if (_sat == 0.0) { - red = _brightness; - green = _brightness; - blue = _brightness; - } - else { - if (_hue >= 360.0) _hue = fmod(_hue, 360); - - int slice = (int)(_hue / 60.0); - float hue_frac = (_hue / 60.0) - slice; - - float aa = _brightness * (1.0 - _sat); - float bb = _brightness * (1.0 - _sat * hue_frac); - float cc = _brightness * (1.0 - _sat * (1.0 - hue_frac)); - - switch(slice) { - case 0: - red = _brightness; - green = cc; - blue = aa; - break; - case 1: - red = bb; - green = _brightness; - blue = aa; - break; - case 2: - red = aa; - green = _brightness; - blue = cc; - break; - case 3: - red = aa; - green = bb; - blue = _brightness; - break; - case 4: - red = cc; - green = aa; - blue = _brightness; - break; - case 5: - red = _brightness; - green = aa; - blue = bb; - break; - default: - red = 0.0; - green = 0.0; - blue = 0.0; - break; - } - } - - return (uint32_t)((uint8_t)(red * 255.0) << 16) | ((uint8_t)(green * 255.0) << 8) | ((uint8_t)(blue * 255.0)); -} - -// Convert HSB color to 24-bit color representation -// _hue: 0 ~ 359 -// _sat: 0 ~ 255 -// _bri: 0 ~ 255 -//======================================================= -uint32_t hsb_to_rgb_int(int hue, int sat, int brightness) -{ - float _hue = (float)hue; - float _sat = (float)((float)sat / 1000.0); - float _brightness = (float)((float)brightness / 1000.0); - float red = 0.0; - float green = 0.0; - float blue = 0.0; - - if (_sat == 0.0) { - red = _brightness; - green = _brightness; - blue = _brightness; - } - else { - if (_hue >= 360.0) _hue = fmod(_hue, 360); - - int slice = (int)(_hue / 60.0); - float hue_frac = (_hue / 60.0) - slice; - - float aa = _brightness * (1.0 - _sat); - float bb = _brightness * (1.0 - _sat * hue_frac); - float cc = _brightness * (1.0 - _sat * (1.0 - hue_frac)); - - switch(slice) { - case 0: - red = _brightness; - green = cc; - blue = aa; - break; - case 1: - red = bb; - green = _brightness; - blue = aa; - break; - case 2: - red = aa; - green = _brightness; - blue = cc; - break; - case 3: - red = aa; - green = bb; - blue = _brightness; - break; - case 4: - red = cc; - green = aa; - blue = _brightness; - break; - case 5: - red = _brightness; - green = aa; - blue = bb; - break; - default: - red = 0.0; - green = 0.0; - blue = 0.0; - break; - } - } - - return (uint32_t)((uint8_t)(red * 255.0) << 16) | ((uint8_t)(green * 255.0) << 8) | ((uint8_t)(blue * 255.0)); -} - diff --git a/ports/esp32/libs/neopixel.h b/ports/esp32/libs/neopixel.h deleted file mode 100644 index c45f33b5d6..0000000000 --- a/ports/esp32/libs/neopixel.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of the MicroPython ESP32 project, https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo - * - * The MIT License (MIT) - * - * Copyright (c) 2018 LoBo (https://github.com/loboris) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include "driver/gpio.h" -#include "driver/rmt.h" - -#define DIVIDER 4 // 80 MHz clock divider -#define RMT_DURATION_NS 12.5 // minimum time of a single RMT duration based on 80 MHz clock (ns) -#define RMT_PERIOD_NS 50 // minimum bit time based on 80 MHz clock and divider of 4 -#define RTM_PIXEL_BUFFER_SIZE 1 // -#define MAX_PULSES 32 // A channel has a 64 "pulse" buffer - we use half per pass - -typedef struct bit_timing { - uint8_t level0; - uint16_t duration0; - uint8_t level1; - uint16_t duration1; -} bit_timing_t; - -typedef struct pixel_timing { - bit_timing_t mark; - bit_timing_t space; - bit_timing_t reset; -} pixel_timing_t; - -typedef struct pixel_settings { - uint8_t *pixels; // buffer containing pixel values, 3 (RGB) or 4 (RGBW) bytes per pixel - pixel_timing_t timings; // timing data from which the pixels BIT data are formed - uint16_t pixel_count; // number of used pixels - uint8_t brightness; // brightness factor applied to pixel color - char color_order[5]; - uint8_t nbits; // number of bits used (24 for RGB devices, 32 for RGBW devices) -} pixel_settings_t; - -void np_set_pixel_color(pixel_settings_t *px, uint16_t idx, uint32_t color); -void np_set_pixel_color_hsb(pixel_settings_t *px, uint16_t idx, float hue, float saturation, float brightness); -uint32_t np_get_pixel_color(pixel_settings_t *px, uint16_t idx, uint8_t *white); -void np_show(pixel_settings_t *px, rmt_channel_t channel); -void np_clear(pixel_settings_t *px); - -int neopixel_init(int gpioNum, rmt_channel_t channel); -void neopixel_deinit(rmt_channel_t channel); - -void rgb_to_hsb( uint32_t color, float *hue, float *sat, float *bri ); -uint32_t hsb_to_rgb(float hue, float saturation, float brightness); -uint32_t hsb_to_rgb_int(int hue, int sat, int brightness); diff --git a/ports/esp32/m5stackcore2/boot.py b/ports/esp32/m5stackcore2/boot.py deleted file mode 100644 index 744bf57a83..0000000000 --- a/ports/esp32/m5stackcore2/boot.py +++ /dev/null @@ -1,39 +0,0 @@ -import axp192 -import kv -import uos - -try: - # for m5stack-core2 only - axp = axp192.Axp192() - axp.powerAll() - axp.setLCDBrightness(80) # 设置背光亮度 0~100 -except OSError: - print("make sure axp192.py is in libs folder") - - -def _on_get_url(url): - kv.set('_amp_pyapp_url', url) - execfile('/lib/appOta.py') - - -def _connect_wifi(ssid, passwd): - import network - sta_if = network.WLAN(network.STA_IF) - if not sta_if.isconnected(): - sta_if.active(True) - sta_if.scan() - sta_if.connect(ssid, passwd) - - -bt_disabled = kv.get('disable_bt') -if bt_disabled != "no": - uos.plussys_mm() - -channel = kv.get('app_upgrade_channel') -if channel != "disable": - ssid = kv.get('_amp_wifi_ssid') - passwd = kv.get('_amp_wifi_passwd') - if isinstance(ssid, str) and isinstance(passwd, str): - _connect_wifi(ssid, passwd) - import online_upgrade - online_upgrade.on(_on_get_url) diff --git a/ports/esp32/m5stackcore2/lib/axp192.py b/ports/esp32/m5stackcore2/lib/axp192.py deleted file mode 100644 index 8b4d049ae4..0000000000 --- a/ports/esp32/m5stackcore2/lib/axp192.py +++ /dev/null @@ -1,248 +0,0 @@ -from micropython import const -import ustruct -import i2c_bus -import utime as time -# import deviceCfg - -_BTN_IRQ = const(0x46) - -def map_value(value, input_min, input_max, aims_min, aims_max): - value = min(max(input_min, value), input_max) - value_deal = (value - input_min) * (aims_max - aims_min) / (input_max - input_min) + aims_min - return value_deal - -class Axp192: - - CURRENT_100MA = const(0b0000) - CURRENT_190MA = const(0b0001) - CURRENT_280MA = const(0b0010) - CURRENT_360MA = const(0b0011) - CURRENT_450MA = const(0b0100) - CURRENT_550MA = const(0b0101) - CURRENT_630MA = const(0b0110) - CURRENT_700MA = const(0b0111) - - def __init__(self): - self.addr = 0x34 - self.i2c = i2c_bus.get(i2c_bus.M_BUS, device_in=True) - - def powerAll(self): - regchar = self._regChar - # axp: vbus limit off - regchar(0x30, (self._read8bit(0x30) & 0x04) | 0x02) - # AXP192 GPIO1:OD OUTPUT - regchar(0x92, self._read8bit(0x92) & 0xf8) - # AXP192 GPIO2:OD OUTPUT - regchar(0x93, self._read8bit(0x93) & 0xf8) - # AXP192 RTC CHG - regchar(0x35, (self._read8bit(0x35) & 0x1c) | 0xa3) - # ESP voltage:3.35V - self.setESPVoltage(3350) - # LCD backlight voltage:3.3V - # self.setLCDBacklightVoltage(3300) - # Periph power voltage preset (LCD_logic, SD card) - self.setLDOVoltage(2, 3300) - # Vibrator power voltage preset - self.setLDOVoltage(3, 2000) - # Eanble LCD SD power - self.setLDOEnable(2, True) - # Set charge current:100ma - # self.setChargeCurrent(CURRENT_100MA) - # self.setChargeCurrent(deviceCfg.get_bat_charge_current()) - - # AXP192 GPIO4 - regchar(0x95, (self._read8bit(0x95) & 0x72) | 0x84) - - regchar(0x36, 0x4C) - regchar(0x82, 0xff) - - self.setLCDReset(0) - time.sleep(0.1) - self.setLCDReset(1) - time.sleep(0.1) - - #if deviceCfg.get_comx_status(): - # self.setBusPowerMode(1) # disable M-Bus 5V output if use COM.X module. - #else: - self.setBusPowerMode(0) # enable M-Bus 5V output as default. - - def powerOff(self): - self._regChar(0x32, self._regChar(0x32) | 0x80) - -# AXP192 Status getting function - - def getTempInAXP192(self): - return (self._read12Bit(0x5e)) * 0.1 - 144.7 - - def getChargeState(self): - return True if self._regChar(0x01) & 0x40 else False - - def getBatVoltage(self): - return (self._read12Bit(0x78)) * 1.1 / 1000 - - def getBatCurrent(self): - currentIn = self._read13Bit(0x7A) - currentOut = self._read13Bit(0x7C) - return (currentIn - currentOut) * 0.5 - - def getVinVoltage(self): - return (self._read12Bit(0x56)) * 1.7 / 1000 - - def getVinCurrent(self): - return (self._read12Bit(0x58)) * 0.625 - - def getVBusVoltage(self): - return (self._read12Bit(0x5A)) * 1.7 / 1000 - - def getVBusCurrent(self): - return (self._read12Bit(0x5C)) * 0.375 - - -# AXP192 Status setting function - - def setChargeState(self, state): - pass - - def setChargeCurrent(self, current): - buf = self._regChar(0x33) - buf = (buf & 0xf0) | (current & 0x0f) - self._regChar(0x33, buf) - - def setBusPowerMode(self, mode): - """ - 0: M-BUS 5V output mode. - 1: M-BUS 5V input mode. - """ - if mode == 0: - self._regChar(0x91, (self._read8bit(0x91) & 0x0F) | 0xF0) - self._regChar(0x90, (self._read8bit(0x90) & 0xF8) | 0x02) - self._regChar(0x12, (self._read8bit(0x12) | 0x40)) - else: - self._regChar(0x12, self._read8bit(0x12) & 0xBF) - self._regChar(0x90, (self._read8bit(0x90) & 0xF8) | 0x01) - - def setLDOVoltage(self, number, voltage): - # print("number: " + str(number) + " voltage: " + str(voltage)) - if voltage > 3300: - vol = 15 - else: - vol = (int)((voltage / 100) - 18) - regchar = self._regChar - if number == 2: - regchar(0x28, ((self._read8bit(0x28) & 0x0F) | (vol << 4))) - if number == 3: - regchar(0x28, ((self._read8bit(0x28) & 0xF0) | vol)) - - def setLDOEnable(self, number, state): - mask = 0x01 - if number < 2 or number > 3: - return - mask = mask << number - if(state): - self._regChar(0x12, self._read8bit(0x12) | mask) - else: - self._regChar(0x12, self._read8bit(0x12) & (~ mask)) - - def setDCVoltage(self, number, voltage): - addr = [0, 0x26, 0x25, 0x27] - regchar = self._regChar - if number < 1 and number > 3: - return - vol = (int)(0 if voltage < 700 else (voltage - 700) / 25) - regchar(addr[number], (self._read8bit(addr[number]) & 0x80) | (vol & 0x7F)) - - def disableAllIRQ(self): - for i in [0x40, 0x41, 0x42, 0x43, 0x4a]: - self._regChar(i, 0x00) - - def clearAllIRQ(self): - for i in [0x44, 0x45, 0x46, 0x47, 0x4d]: - self._regChar(i, 0xff) - - def enableBtnIRQ(self): - self._regChar(0x42, 0x02) - - # ESP32 Voltage - def setESPVoltage(self, voltage): - if voltage >= 3000 and voltage <= 3400: - self.setDCVoltage(1, voltage) - - # LCD backlight Voltage - def setLCDBacklightVoltage(self, voltage): - voltage = voltage * 1000 - if voltage >= 2400 and voltage <= 3300: - self.setDCVoltage(3, voltage) - - def setLCDEnable(self, state): - self.setLDOEnable(2, state) - - # LCD Brightness - def setLCDBrightness(self, brightness): - vol = map_value(brightness, 0, 100, 2400, 3300) - self.setDCVoltage(3, vol) - - # LCD Reset - def setLCDReset(self, state): - mask = 0x02 - if state: - self._regChar(0x96, self._read8bit(0x96) | mask) - else: - self._regChar(0x96, self._read8bit(0x96) & (~ mask)) - - # Set Power LED - def setPowerLED(self, state): - if state: - self._regChar(0x94, self._read8bit(0x94) & 0xFD) - else: - self._regChar(0x94, self._read8bit(0x94) | 0x02) - - def setSpkEnable(self, state): - gpio_bit = 0x04 - data = self._read8bit(0x94) - if state: - data = data | gpio_bit - else: - data = data & (~gpio_bit) - self._regChar(0x94, data) - - # It seem not useful for Vibration motor. - # LDO3: 1.8v ~ 3.3v - def setVibrationIntensity(self, value): - vol = map_value(value, 0, 100, 1800, 3300) - self.setLDOVoltage(3, vol) - - def setVibrationEnable(self, state): - self.setLDOEnable(3, state) - -# I2C read and write function - def _regChar(self, reg, value=None, buf=bytearray(1)): - if value is None: - self.i2c.readfrom_mem_into(self.addr, reg, buf) - return buf[0] - - ustruct.pack_into('h' - struct.pack_into(encode, buf, 0, data) - self.i2c.writeto_mem(self.addr, reg, buf) - - def write_u32(self, reg, data, byteorder='big'): - buf = bytearray(4) - encode = 'i' - struct.pack_into(encode, buf, 0, data) - self.i2c.writeto_mem(self.addr, reg, buf) - - def read_u8(self, reg): - return self.i2c.readfrom_mem(self.addr, reg, 1)[0] - - def read_u16(self, reg, byteorder='big'): - buf = bytearray(2) - self.i2c.readfrom_mem_into(self.addr, reg, buf) - encode = 'h' - return struct.unpack(encode, buf)[0] - - def read_u32(self, reg, byteorder='big'): - buf = bytearray(4) - self.i2c.readfrom_mem_into(self.addr, reg, buf) - encode = 'i' - return struct.unpack(encode, buf)[0] - - def read(self, num): - return self.i2c.readfrom(self.addr, num) - - def read_reg(self, reg, num): - return self.i2c.readfrom_mem(self.addr, reg, num) - - @staticmethod - def _get_format_str(format_type): - format_str = '>' if (format_type & (1 << 6)) else '<' - format_str += {1: 'b', 2: 'h', 4: 'i'}.get(format_type & 0x0f) - format_str = format_str.upper() if (format_type & (1 << 4)) else format_str - return format_str - - def write_mem_data(self, reg, data, format_type): - format_str = self._get_format_str(format_type) - buf = bytearray(struct.pack(format_str, data)) - self.i2c.writeto_mem(self.addr, reg, buf) - - def write_data(self, data, format_type): - format_str = self._get_format_str(format_type) - buf = bytearray(struct.pack(format_str, data)) - self.i2c.writeto(self.addr, buf) - - def write_list(self, data): - buf = bytearray(data) - self.i2c.writeto(self.addr, buf) - - def write_mem_list(self, reg, data, num): - buf = bytearray(data) - self.i2c.writeto_mem(self.addr, reg, buf) - - def read_data(self, num, format_type): - format_str = self._get_format_str(format_type) - format_str = format_str[0] + format_str[1] * num - buf = bytearray((format_type & 0x0f) * num) - self.i2c.readfrom_into(self.addr, buf) - return struct.unpack(format_str, buf) - - def read_mem_data(self, reg, num, format_type): - format_str = self._get_format_str(format_type) - format_str = format_str[0] + format_str[1] * num - buf = bytearray((format_type & 0x0f) * num) - self.i2c.readfrom_mem_into(self.addr, reg, buf) - return struct.unpack(format_str, buf) - - def scan(self): - return self.i2c.scan() - - def available(self): - return self.i2c.is_ready(self.addr) - -class Pahub_I2C: - def __init__(self, pos, port=(32, 33), freq=100000): # PORTA (32, 33) - from units import _pahub - self.pahub = _pahub.Pahub(port) - self.i2c = get(port, freq=freq) - self.pos = pos - - def readfrom(self, addr, num): - self.pahub.select_only_on(self.pos) - data = self.i2c.readfrom(addr, num) - return data - - def readfrom_into(self, addr, buf): - buf_in = bytearray(len(buf)) - self.pahub.select_only_on(self.pos) - self.i2c.readfrom_into(addr, buf_in) - for i in range(len(buf)): - buf[i] = buf_in[i] - - def readfrom_mem_into(self, addr, reg, buf): - buf_in = bytearray(len(buf)) - self.pahub.select_only_on(self.pos) - self.i2c.readfrom_mem_into(addr, reg, buf_in) - for i in range(len(buf)): - buf[i] = buf_in[i] - - def readfrom_mem(self, addr, reg, num): - self.pahub.select_only_on(self.pos) - data = self.i2c.readfrom_mem(addr, reg, num) - return data - - def writeto_mem(self, addr, reg, data): - self.pahub.select_only_on(self.pos) - self.i2c.writeto_mem(addr, reg, data) - - def writeto(self, addr, data): - self.pahub.select_only_on(self.pos) - self.i2c.writeto(addr, data) - - def is_ready(self, addr): - self.pahub.select_only_on(self.pos) - data = self.i2c.is_ready(addr) - return data - - def scan(self): - self.pahub.select_only_on(self.pos) - data = self.i2c.scan() - return data - - def available(self): - return self.i2c.is_ready(self.addr) - - def deinit(self): - pass - -class Unit(Exception): - pass - -class UnitI2C: - def __init__(self, port, freq, addr): - self.i2c = easyI2C(port, addr, freq) - - def _check_device(self): - if self.i2c.available() or self.i2c.available(): - pass - else: - raise Unit("{} unit not found".format(self.__qualname__.upper())) - - def deinit(self): - pass - diff --git a/ports/esp32/m5stackcore2/lib/pcm.py b/ports/esp32/m5stackcore2/lib/pcm.py deleted file mode 100644 index b930406ea5..0000000000 --- a/ports/esp32/m5stackcore2/lib/pcm.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- encoding: utf-8 -*- -''' -@File : Player.py -@Time : 2021/12/08 20:32:10 -@Author : zeta.zz -@License : (C)Copyright 2015-2021, M5STACK -@Desc : Player I2S driver. -''' - - -from machine import I2S -from machine import Pin -from axp192 import Axp192 -from micropython import const -import io -import math -import struct -import gc -import urequests - -BCK_PIN = Pin(12) -WS_PIN = Pin(0) -SDOUT_PIN = Pin(2) - -PI = 3.141592653 - -I2S0 = const(0) -F16B = const(16) -F24B = const(24) -F32B = const(32) - - -class Player: - - def __init__(self): - self.power = Axp192() - self.power.powerAll() - - def open(self): - pass - - def play(self, wav_file, rate=None, data_format=None, channel=None): - """ - Parameter: - wav_file - Return: - False Not WAV format file - """ - if type(wav_file) is str: - try: - wav = open(wav_file, 'rb') - except Exception as e: - print('Audio file open caught exception: {} {}'.format( - type(e).__name__, e)) - return - elif type(wav_file) is bytes: - wav = io.BytesIO(len(wav_file)) - wav.write(wav_file) - wav.seek(0) - else: - return "Unknow file type" - - wav_head = wav.read(44) - if wav_head[0:4] != b"RIFF" and wav_head[8:12] != b"WAVE": - return "Wrong WAV format file" - - if rate and data_format and channel: - channels = channel - samplerate = rate - dataformat = data_format - else: - channels = (wav_head[0x17] << 8) | (wav_head[0x16]) - if channels == 1: - channels = I2S.MONO - elif channels == 2: - channels = I2S.STEREO - samplerate = (wav_head[0x1B] << 24) | (wav_head[0x1A] << 16) | (wav_head[0x19] << 8) | (wav_head[0x18]) - dataformat = (wav_head[0x23] << 8) | (wav_head[0x22]) - - audio_out = I2S( - I2S0, - sck=BCK_PIN, ws=WS_PIN, sd=SDOUT_PIN, - mode=I2S.TX, - bits=dataformat, - format=channels, - rate=samplerate, - ibuf=3*1024) - - # advance to first byte of Data section in WAV file - # wav.seek(44) - - # allocate sample arrays - # memoryview used to reduce heap allocation in while loop - wav_samples = bytearray(1024) - wav_samples_mv = memoryview(wav_samples) - - # continuously read audio samples from the WAV file - # and write them to an I2S DAC - self.power.setSpkEnable(True) - try: - while True: - # try: - num_read = wav.readinto(wav_samples_mv) - num_written = 0 - if num_read == 0: - # pos = wav.seek(44) - # exit - break - else: - while num_written < num_read: - num_written += audio_out.write( - wav_samples_mv[num_written:num_read]) - except (KeyboardInterrupt, Exception) as e: - print('Player caught exception: {} {}'.format(type(e).__name__, e)) - self.power.setSpkEnable(False) - raise - finally: - self.power.setSpkEnable(False) - audio_out.deinit() - wav.close() - del wav - del wav_samples_mv - del wav_samples - gc.collect() - - def playCloudWAV(self, url): - """ - Parameter: - url: WAV format file URL - Return: - False - None - """ - request = urequests.get(url) - if (request.status_code) == 200: - self.playWAV(request.content) - else: - return "Request WAV file fail" - - def playTone(self, freq, beta, rate=44100, data_format=F16B, channel=I2S.STEREO): - """ - Parameter: - freq = frequency - duration = time in secods - Return: - """ - wave_data = io.BytesIO() - freq_rate = (freq / rate) - # Calculate a period of sine wave - cycle = rate / freq - for i in range(0, cycle): - # 6.283185 = 2 * PI - x = 6.283185 * freq_rate * i - data = int(32767 * math.sin(x)) - wave_data.write(bytes(struct.pack('h', data))) - - audio_out = I2S( - I2S0, - sck=BCK_PIN, ws=WS_PIN, sd=SDOUT_PIN, - mode=I2S.TX, - bits=data_format, - format=channel, - rate=rate, - ibuf=3 * 1024) - - wave_data.seek(0) - # One cycle sine wave data length - length = (len(wave_data.read())) - wave_data.seek(0) - # Calculate how many cycles - cycles = int((rate * beta) / cycle) - - wave_samples = bytearray(length) - wave_samples_mv = memoryview(wave_samples) - - self.power.setSpkEnable(True) - try: - for i in range(0, cycles): - num_read = wave_data.readinto(wave_samples_mv) - num_written = 0 - if num_read == 0: - wave_data.seek(0) - else: - while num_written < num_read: - num_written += audio_out.write(wave_samples_mv[num_written:num_read]) - # print(num_written) - except (KeyboardInterrupt, Exception) as e: - print('Player caught exception: {} {}'.format(type(e).__name__, e)) - self.power.setSpkEnable(False) - raise - finally: - self.power.setSpkEnable(False) - audio_out.deinit() - wave_data.close() - del wave_data - del wave_samples_mv - del wave_samples - gc.collect() diff --git a/ports/esp32/m5stackcore2/lib/uai.py b/ports/esp32/m5stackcore2/lib/uai.py deleted file mode 100644 index c8ec15e3ce..0000000000 --- a/ports/esp32/m5stackcore2/lib/uai.py +++ /dev/null @@ -1,63 +0,0 @@ -from minicv import ML - -AI_ENGINE_ALIYUN = 1 -AI_ENGINE_NATIVE = 2 - -class AI: - def __init__(self, type=AI_ENGINE_NATIVE, accessKey=None, accessSecret=None, ossEndpoint=None, ossBucket=None): - self.type = type - self.ml = ML() - if (self.type == AI_ENGINE_ALIYUN): - self.ml.open(self.ml.ML_ENGINE_CLOUD) - if not accessKey or not accessSecret: - print('access key can not be null') - return - else: - self.ml.config(accessKey, accessSecret, ossEndpoint, ossBucket) - else: - print('now only support cloud ai, not support nativate ai yet') - print("Please use example: ai = AI(AI.AI_ENGINE_CLOUD, 'Your-Access-Key', 'Your-Access-Secret')") - - # 人脸比对 - def compareFace(self, imagePath, compareFacePath): - self.ml.setInputData(imagePath, compareFacePath) - self.ml.loadNet("FacebodyComparing") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - # 人体检测 - def detectPedestrian(self, imagePath): - self.ml.setInputData(imagePath) - self.ml.loadNet("DetectPedestrian") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - # 水果检测 - def detectFruits(self, imagePath): - self.ml.setInputData(imagePath, None) - self.ml.loadNet("DetectFruits") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - # 车牌识别 - def recognizeLicensePlate(self, imagePath): - self.ml.setInputData(imagePath) - self.ml.loadNet("RecognizeLicensePlate") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - def __del__(self): - try: - self.ml.close() - del self.type - del self.ml - except Exception: - pass diff --git a/ports/esp32/machine_adc.c b/ports/esp32/machine_adc.c index cb45aab339..6731978e28 100644 --- a/ports/esp32/machine_adc.c +++ b/ports/esp32/machine_adc.c @@ -4,7 +4,6 @@ * The MIT License (MIT) * * Copyright (c) 2017 Nick Moore - * Copyright (c) 2021 Jonathan Hogg * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,6 +24,7 @@ * THE SOFTWARE. */ + #include #include "esp_log.h" @@ -35,225 +35,176 @@ #include "py/runtime.h" #include "py/mphal.h" #include "modmachine.h" -#include "machine_adc.h" -#define ADCBLOCK1 (&madcblock_obj[0]) -#define ADCBLOCK2 (&madcblock_obj[1]) +typedef struct _madc_obj_t { + mp_obj_base_t base; + gpio_num_t gpio_id; + adc1_channel_t adc1_id; +} madc_obj_t; STATIC const madc_obj_t madc_obj[] = { #if CONFIG_IDF_TARGET_ESP32 - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_36}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_37}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_38}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_39}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_32}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_33}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_34}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_35}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_4}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_0}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_2}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_15}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_13}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_12}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_6, GPIO_NUM_14}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_27}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_25}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_26}, + {{&machine_adc_type}, GPIO_NUM_36, ADC1_CHANNEL_0}, + {{&machine_adc_type}, GPIO_NUM_37, ADC1_CHANNEL_1}, + {{&machine_adc_type}, GPIO_NUM_38, ADC1_CHANNEL_2}, + {{&machine_adc_type}, GPIO_NUM_39, ADC1_CHANNEL_3}, + {{&machine_adc_type}, GPIO_NUM_32, ADC1_CHANNEL_4}, + {{&machine_adc_type}, GPIO_NUM_33, ADC1_CHANNEL_5}, + {{&machine_adc_type}, GPIO_NUM_34, ADC1_CHANNEL_6}, + {{&machine_adc_type}, GPIO_NUM_35, ADC1_CHANNEL_7}, #elif CONFIG_IDF_TARGET_ESP32C3 - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_2}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_3}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_4}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_5}, + {{&machine_adc_type}, GPIO_NUM_0, ADC1_CHANNEL_0}, + {{&machine_adc_type}, GPIO_NUM_1, ADC1_CHANNEL_1}, + {{&machine_adc_type}, GPIO_NUM_2, ADC1_CHANNEL_2}, + {{&machine_adc_type}, GPIO_NUM_3, ADC1_CHANNEL_3}, + {{&machine_adc_type}, GPIO_NUM_4, ADC1_CHANNEL_4}, #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_1}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_2}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_3}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_4}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_5}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_6}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_7}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_8}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_8, GPIO_NUM_9}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_9, GPIO_NUM_10}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_11}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_12}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_13}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_14}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_15}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_16}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_6, GPIO_NUM_17}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_18}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_19}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_20}, + {{&machine_adc_type}, GPIO_NUM_1, ADC1_CHANNEL_0}, + {{&machine_adc_type}, GPIO_NUM_2, ADC1_CHANNEL_1}, + {{&machine_adc_type}, GPIO_NUM_3, ADC1_CHANNEL_2}, + {{&machine_adc_type}, GPIO_NUM_4, ADC1_CHANNEL_3}, + {{&machine_adc_type}, GPIO_NUM_5, ADC1_CHANNEL_4}, + {{&machine_adc_type}, GPIO_NUM_6, ADC1_CHANNEL_5}, + {{&machine_adc_type}, GPIO_NUM_7, ADC1_CHANNEL_6}, + {{&machine_adc_type}, GPIO_NUM_8, ADC1_CHANNEL_7}, + {{&machine_adc_type}, GPIO_NUM_9, ADC1_CHANNEL_8}, + {{&machine_adc_type}, GPIO_NUM_10, ADC1_CHANNEL_9}, #endif }; -// These values are initialised to 0, which means the corresponding ADC channel is not initialised. -// The madc_atten_get/madc_atten_set functions store (atten+1) here so that the uninitialised state -// can be distinguished from the initialised state. -STATIC uint8_t madc_obj_atten[MP_ARRAY_SIZE(madc_obj)]; +STATIC uint8_t adc_bit_width; -static inline adc_atten_t madc_atten_get(const madc_obj_t *self) { - uint8_t value = madc_obj_atten[self - &madc_obj[0]]; - return value == 0 ? ADC_ATTEN_MAX : value - 1; -} +STATIC mp_obj_t madc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, + const mp_obj_t *args) { -static inline void madc_atten_set(const madc_obj_t *self, adc_atten_t atten) { - madc_obj_atten[self - &madc_obj[0]] = atten + 1; -} + static int initialized = 0; + if (!initialized) { + #if CONFIG_IDF_TARGET_ESP32S2 + adc1_config_width(ADC_WIDTH_BIT_13); + #else + adc1_config_width(ADC_WIDTH_BIT_12); + #endif + adc_bit_width = 12; + initialized = 1; + } -const madc_obj_t *madc_search_helper(madcblock_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id) { + mp_arg_check_num(n_args, n_kw, 1, 1, true); + gpio_num_t pin_id = machine_pin_get_id(args[0]); + const madc_obj_t *self = NULL; for (int i = 0; i < MP_ARRAY_SIZE(madc_obj); i++) { - const madc_obj_t *adc = &madc_obj[i]; - if ((block == NULL || block == adc->block) && (channel_id == -1 || channel_id == adc->channel_id) && (gpio_id == -1 || gpio_id == adc->gpio_id)) { - return adc; + if (pin_id == madc_obj[i].gpio_id) { + self = &madc_obj[i]; + break; } } - return NULL; -} - -STATIC void madc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "ADC(Pin(%u), atten=%u)", self->gpio_id, madc_atten_get(self)); -} - -STATIC void madc_atten_helper(const madc_obj_t *self, mp_int_t atten) { - esp_err_t err; - if (self->block->unit_id == ADC_UNIT_1) { - err = adc1_config_channel_atten(self->channel_id, atten); - } else { - err = adc2_config_channel_atten(self->channel_id, atten); - } - if (err != ESP_OK) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid atten")); - } - madc_atten_set(self, atten); -} - -void madc_init_helper(const madc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { - ARG_atten, - }; - - static const mp_arg_t allowed_args[] = { - { MP_QSTR_atten, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - mp_int_t atten = args[ARG_atten].u_int; - if (atten != -1) { - madc_atten_helper(self, atten); - } else if (madc_atten_get(self) == ADC_ATTEN_MAX) { - madc_atten_helper(self, ADC_ATTEN_DB_0); - } -} - -STATIC mp_obj_t madc_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { - mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); - gpio_num_t gpio_id = machine_pin_get_id(args[0]); - const madc_obj_t *self = madc_search_helper(NULL, -1, gpio_id); if (!self) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); + mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for ADC")); } - - if (self->block->width == -1) { - madcblock_bits_helper(self->block, self->block->bits); + esp_err_t err = adc1_config_channel_atten(self->adc1_id, ADC_ATTEN_0db); + if (err == ESP_OK) { + return MP_OBJ_FROM_PTR(self); } - - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); - madc_init_helper(self, n_pos_args - 1, args + 1, &kw_args); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t madc_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - const madc_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - madc_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madc_init_obj, 1, madc_init); - -STATIC mp_obj_t madc_block(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(self->block); + mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_block_obj, madc_block); -STATIC mp_obj_t madc_read(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t raw = madcblock_read_helper(self->block, self->channel_id); - return MP_OBJ_NEW_SMALL_INT(raw); +STATIC void madc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + madc_obj_t *self = self_in; + mp_printf(print, "ADC(Pin(%u))", self->gpio_id); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_obj, madc_read); +// read_u16() STATIC mp_obj_t madc_read_u16(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_uint_t raw = madcblock_read_helper(self->block, self->channel_id); + madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint32_t raw = adc1_get_raw(self->adc1_id); // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) - mp_int_t bits = self->block->bits; - mp_uint_t u16 = raw << (16 - bits) | raw >> (2 * bits - 16); + uint32_t u16 = raw << (16 - adc_bit_width) | raw >> (2 * adc_bit_width - 16); return MP_OBJ_NEW_SMALL_INT(u16); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_u16_obj, madc_read_u16); -STATIC mp_obj_t madc_read_uv(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - adc_atten_t atten = madc_atten_get(self); - return MP_OBJ_NEW_SMALL_INT(madcblock_read_uv_helper(self->block, self->channel_id, atten)); +// Legacy method +STATIC mp_obj_t madc_read(mp_obj_t self_in) { + madc_obj_t *self = self_in; + int val = adc1_get_raw(self->adc1_id); + if (val == -1) { + mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); + } + return MP_OBJ_NEW_SMALL_INT(val); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_uv_obj, madc_read_uv); +MP_DEFINE_CONST_FUN_OBJ_1(madc_read_obj, madc_read); STATIC mp_obj_t madc_atten(mp_obj_t self_in, mp_obj_t atten_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t atten = mp_obj_get_int(atten_in); - madc_atten_helper(self, atten); - return mp_const_none; + madc_obj_t *self = self_in; + adc_atten_t atten = mp_obj_get_int(atten_in); + esp_err_t err = adc1_config_channel_atten(self->adc1_id, atten); + if (err == ESP_OK) { + return mp_const_none; + } + mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); } MP_DEFINE_CONST_FUN_OBJ_2(madc_atten_obj, madc_atten); -STATIC mp_obj_t madc_width(mp_obj_t self_in, mp_obj_t bits_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t bits = mp_obj_get_int(bits_in); - madcblock_bits_helper(self->block, bits); +STATIC mp_obj_t madc_width(mp_obj_t cls_in, mp_obj_t width_in) { + adc_bits_width_t width = mp_obj_get_int(width_in); + esp_err_t err = adc1_config_width(width); + if (err != ESP_OK) { + mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); + } + switch (width) { + #if CONFIG_IDF_TARGET_ESP32 + case ADC_WIDTH_9Bit: + adc_bit_width = 9; + break; + case ADC_WIDTH_10Bit: + adc_bit_width = 10; + break; + case ADC_WIDTH_11Bit: + adc_bit_width = 11; + break; + case ADC_WIDTH_12Bit: + adc_bit_width = 12; + break; + #elif CONFIG_IDF_TARGET_ESP32S2 + case ADC_WIDTH_BIT_13: + adc_bit_width = 13; + break; + #elif CONFIG_IDF_TARGET_ESP32S3 + case ADC_WIDTH_BIT_12: + adc_bit_width = 12; + break; + #endif + default: + break; + } return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_2(madc_width_obj, madc_width); +MP_DEFINE_CONST_FUN_OBJ_2(madc_width_fun_obj, madc_width); +MP_DEFINE_CONST_CLASSMETHOD_OBJ(madc_width_obj, MP_ROM_PTR(&madc_width_fun_obj)); STATIC const mp_rom_map_elem_t madc_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&madc_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_block), MP_ROM_PTR(&madc_block_obj) }, - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&madc_read_obj) }, { MP_ROM_QSTR(MP_QSTR_read_u16), MP_ROM_PTR(&madc_read_u16_obj) }, - { MP_ROM_QSTR(MP_QSTR_read_uv), MP_ROM_PTR(&madc_read_uv_obj) }, - // Legacy API methods: + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&madc_read_obj) }, { MP_ROM_QSTR(MP_QSTR_atten), MP_ROM_PTR(&madc_atten_obj) }, { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&madc_width_obj) }, - { MP_ROM_QSTR(MP_QSTR_ATTN_0DB), MP_ROM_INT(ADC_ATTEN_DB_0) }, - { MP_ROM_QSTR(MP_QSTR_ATTN_2_5DB), MP_ROM_INT(ADC_ATTEN_DB_2_5) }, - { MP_ROM_QSTR(MP_QSTR_ATTN_6DB), MP_ROM_INT(ADC_ATTEN_DB_6) }, - { MP_ROM_QSTR(MP_QSTR_ATTN_11DB), MP_ROM_INT(ADC_ATTEN_DB_11) }, + { MP_ROM_QSTR(MP_QSTR_ATTN_0DB), MP_ROM_INT(ADC_ATTEN_0db) }, + { MP_ROM_QSTR(MP_QSTR_ATTN_2_5DB), MP_ROM_INT(ADC_ATTEN_2_5db) }, + { MP_ROM_QSTR(MP_QSTR_ATTN_6DB), MP_ROM_INT(ADC_ATTEN_6db) }, + { MP_ROM_QSTR(MP_QSTR_ATTN_11DB), MP_ROM_INT(ADC_ATTEN_11db) }, #if CONFIG_IDF_TARGET_ESP32 - { MP_ROM_QSTR(MP_QSTR_WIDTH_9BIT), MP_ROM_INT(9) }, - { MP_ROM_QSTR(MP_QSTR_WIDTH_10BIT), MP_ROM_INT(10) }, - { MP_ROM_QSTR(MP_QSTR_WIDTH_11BIT), MP_ROM_INT(11) }, - #endif - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - { MP_ROM_QSTR(MP_QSTR_WIDTH_12BIT), MP_ROM_INT(12) }, - #endif - #if CONFIG_IDF_TARGET_ESP32S2 - { MP_ROM_QSTR(MP_QSTR_WIDTH_13BIT), MP_ROM_INT(13) }, + { MP_ROM_QSTR(MP_QSTR_WIDTH_9BIT), MP_ROM_INT(ADC_WIDTH_9Bit) }, + { MP_ROM_QSTR(MP_QSTR_WIDTH_10BIT), MP_ROM_INT(ADC_WIDTH_10Bit) }, + { MP_ROM_QSTR(MP_QSTR_WIDTH_11BIT), MP_ROM_INT(ADC_WIDTH_11Bit) }, + { MP_ROM_QSTR(MP_QSTR_WIDTH_12BIT), MP_ROM_INT(ADC_WIDTH_12Bit) }, + #elif CONFIG_IDF_TARGET_ESP32S2 + { MP_ROM_QSTR(MP_QSTR_WIDTH_13BIT), MP_ROM_INT(ADC_WIDTH_BIT_13) }, + #elif CONFIG_IDF_TARGET_ESP32S3 + { MP_ROM_QSTR(MP_QSTR_WIDTH_12BIT), MP_ROM_INT(ADC_WIDTH_BIT_12) }, #endif - }; + STATIC MP_DEFINE_CONST_DICT(madc_locals_dict, madc_locals_dict_table); const mp_obj_type_t machine_adc_type = { diff --git a/ports/esp32/machine_adc.h b/ports/esp32/machine_adc.h deleted file mode 100644 index 0f229a2c5c..0000000000 --- a/ports/esp32/machine_adc.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef MICROPY_INCLUDED_MACHINE_ADC_H -#define MICROPY_INCLUDED_MACHINE_ADC_H - -#include "machine_adcblock.h" - -typedef struct _madc_obj_t { - mp_obj_base_t base; - madcblock_obj_t *block; - adc_channel_t channel_id; - gpio_num_t gpio_id; -} madc_obj_t; - -extern const madc_obj_t *madc_search_helper(madcblock_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id); -extern void madc_init_helper(const madc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args); - -#endif // MICROPY_INCLUDED_MACHINE_ADC_H diff --git a/ports/esp32/machine_adcblock.c b/ports/esp32/machine_adcblock.c deleted file mode 100644 index 06c215f8ae..0000000000 --- a/ports/esp32/machine_adcblock.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Jonathan Hogg - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include - -#include "esp_log.h" - -#include "driver/gpio.h" -#include "driver/adc.h" - -#include "py/runtime.h" -#include "py/mphal.h" -#include "modmachine.h" -#include "machine_adc.h" -#include "machine_adcblock.h" - -#define DEFAULT_VREF 1100 - -madcblock_obj_t madcblock_obj[] = { - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - {{&machine_adcblock_type}, ADC_UNIT_1, 12, -1, {0}}, - {{&machine_adcblock_type}, ADC_UNIT_2, 12, -1, {0}}, - #elif CONFIG_IDF_TARGET_ESP32S2 - {{&machine_adcblock_type}, ADC_UNIT_1, 13, -1, {0}}, - {{&machine_adcblock_type}, ADC_UNIT_2, 13, -1, {0}}, - #endif -}; - -STATIC void madcblock_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - madcblock_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "ADCBlock(%u, bits=%u)", self->unit_id, self->bits); -} - -void madcblock_bits_helper(madcblock_obj_t *self, mp_int_t bits) { - switch (bits) { - #if CONFIG_IDF_TARGET_ESP32 - case 9: - self->width = ADC_WIDTH_BIT_9; - break; - case 10: - self->width = ADC_WIDTH_BIT_10; - break; - case 11: - self->width = ADC_WIDTH_BIT_11; - break; - #endif - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - case 12: - self->width = ADC_WIDTH_BIT_12; - break; - #endif - #if CONFIG_IDF_TARGET_ESP32S2 - case 13: - self->width = ADC_WIDTH_BIT_13; - break; - #endif - default: - mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); - } - self->bits = bits; - - if (self->unit_id == ADC_UNIT_1) { - adc1_config_width(self->width); - } - for (adc_atten_t atten = ADC_ATTEN_DB_0; atten < ADC_ATTEN_MAX; atten++) { - if (self->characteristics[atten] != NULL) { - esp_adc_cal_characterize(self->unit_id, atten, self->width, DEFAULT_VREF, self->characteristics[atten]); - } - } -} - -STATIC void madcblock_init_helper(madcblock_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { - ARG_bits, - }; - - static const mp_arg_t allowed_args[] = { - { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - mp_int_t bits = args[ARG_bits].u_int; - if (bits != -1) { - madcblock_bits_helper(self, bits); - } else if (self->width == -1) { - madcblock_bits_helper(self, self->bits); - } -} - -STATIC mp_obj_t madcblock_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { - mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); - adc_unit_t unit = mp_obj_get_int(args[0]); - madcblock_obj_t *self = NULL; - for (int i = 0; i < MP_ARRAY_SIZE(madcblock_obj); i++) { - if (unit == madcblock_obj[i].unit_id) { - self = &madcblock_obj[i]; - break; - } - } - if (!self) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid block id")); - } - - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); - madcblock_init_helper(self, n_pos_args - 1, args + 1, &kw_args); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t madcblock_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - madcblock_obj_t *self = pos_args[0]; - madcblock_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madcblock_init_obj, 1, madcblock_init); - -STATIC mp_obj_t madcblock_connect(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - madcblock_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - adc_channel_t channel_id = -1; - gpio_num_t gpio_id = -1; - if (n_pos_args == 2) { - if (mp_obj_is_int(pos_args[1])) { - channel_id = mp_obj_get_int(pos_args[1]); - } else { - gpio_id = machine_pin_get_id(pos_args[1]); - } - } else if (n_pos_args == 3) { - channel_id = mp_obj_get_int(pos_args[1]); - gpio_id = machine_pin_get_id(pos_args[2]); - } else { - mp_raise_TypeError(MP_ERROR_TEXT("too many positional args")); - } - - const madc_obj_t *adc = madc_search_helper(self, channel_id, gpio_id); - if (adc != NULL) { - madc_init_helper(adc, 0, pos_args + n_pos_args, kw_args); - return MP_OBJ_FROM_PTR(adc); - } - mp_raise_ValueError(MP_ERROR_TEXT("no matching ADC")); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madcblock_connect_obj, 2, madcblock_connect); - -mp_int_t madcblock_read_helper(madcblock_obj_t *self, adc_channel_t channel_id) { - int raw; - if (self->unit_id == ADC_UNIT_1) { - raw = adc1_get_raw(channel_id); - } else { - check_esp_err(adc2_get_raw(channel_id, self->width, &raw)); - } - return raw; -} - -mp_int_t madcblock_read_uv_helper(madcblock_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) { - int raw = madcblock_read_helper(self, channel_id); - esp_adc_cal_characteristics_t *adc_chars = self->characteristics[atten]; - if (adc_chars == NULL) { - adc_chars = malloc(sizeof(esp_adc_cal_characteristics_t)); - esp_adc_cal_characterize(self->unit_id, atten, self->width, DEFAULT_VREF, adc_chars); - self->characteristics[atten] = adc_chars; - } - mp_int_t uv = esp_adc_cal_raw_to_voltage(raw, adc_chars) * 1000; - return uv; -} - -STATIC const mp_rom_map_elem_t madcblock_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&madcblock_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&madcblock_connect_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(madcblock_locals_dict, madcblock_locals_dict_table); - -const mp_obj_type_t machine_adcblock_type = { - { &mp_type_type }, - .name = MP_QSTR_ADCBlock, - .print = madcblock_print, - .make_new = madcblock_make_new, - .locals_dict = (mp_obj_t)&madcblock_locals_dict, -}; diff --git a/ports/esp32/machine_adcblock.h b/ports/esp32/machine_adcblock.h deleted file mode 100644 index 0500726d71..0000000000 --- a/ports/esp32/machine_adcblock.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef MICROPY_INCLUDED_MACHINE_ADCBLOCK_H -#define MICROPY_INCLUDED_MACHINE_ADCBLOCK_H - -#include "esp_adc_cal.h" - -typedef struct _madcblock_obj_t { - mp_obj_base_t base; - adc_unit_t unit_id; - mp_int_t bits; - adc_bits_width_t width; - esp_adc_cal_characteristics_t *characteristics[ADC_ATTEN_MAX]; -} madcblock_obj_t; - -extern madcblock_obj_t madcblock_obj[]; - -extern void madcblock_bits_helper(madcblock_obj_t *self, mp_int_t bits); -extern mp_int_t madcblock_read_helper(madcblock_obj_t *self, adc_channel_t channel_id); -extern mp_int_t madcblock_read_uv_helper(madcblock_obj_t *self, adc_channel_t channel_id, adc_atten_t atten); - -#endif // MICROPY_INCLUDED_MACHINE_ADCBLOCK_H diff --git a/ports/esp32/machine_bitstream.c b/ports/esp32/machine_bitstream.c index 9d2bb57246..4284b5f8ba 100644 --- a/ports/esp32/machine_bitstream.c +++ b/ports/esp32/machine_bitstream.c @@ -24,16 +24,19 @@ * THE SOFTWARE. */ -// This is a translation of the cycle counter implementation in ports/stm32/machine_bitstream.c. - #include "py/mpconfig.h" #include "py/mphal.h" +#include "modesp32.h" #if MICROPY_PY_MACHINE_BITSTREAM +/******************************************************************************/ +// Bit-bang implementation + #define NS_TICKS_OVERHEAD (6) -void IRAM_ATTR machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { +// This is a translation of the cycle counter implementation in ports/stm32/machine_bitstream.c. +STATIC void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { uint32_t pin_mask, gpio_reg_set, gpio_reg_clear; #if !CONFIG_IDF_TARGET_ESP32C3 if (pin >= 32) { @@ -83,4 +86,125 @@ void IRAM_ATTR machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing mp_hal_quiet_timing_exit(irq_state); } +/******************************************************************************/ +// RMT implementation + +#include "driver/rmt.h" + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) +// This convenience macro was not available in earlier IDF versions. +#define RMT_DEFAULT_CONFIG_TX(gpio, channel_id) \ + { \ + .rmt_mode = RMT_MODE_TX, \ + .channel = channel_id, \ + .clk_div = 80, \ + .gpio_num = gpio, \ + .mem_block_num = 1, \ + .tx_config = { \ + .loop_en = false, \ + .carrier_freq_hz = 38000, \ + .carrier_duty_percent = 33, \ + .carrier_level = RMT_CARRIER_LEVEL_HIGH, \ + .carrier_en = false, \ + .idle_level = RMT_IDLE_LEVEL_LOW, \ + .idle_output_en = true, \ + } \ + } +#endif + +// Logical 0 and 1 values (encoded as a rmt_item32_t). +// The duration fields will be set later. +STATIC rmt_item32_t bitstream_high_low_0 = {{{ 0, 1, 0, 0 }}}; +STATIC rmt_item32_t bitstream_high_low_1 = {{{ 0, 1, 0, 0 }}}; + +// See https://github.com/espressif/esp-idf/blob/master/examples/common_components/led_strip/led_strip_rmt_ws2812.c +// This is called automatically by the IDF during rmt_write_sample in order to +// convert the byte stream to rmt_item32_t's. +STATIC void IRAM_ATTR bitstream_high_low_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num) { + if (src == NULL || dest == NULL) { + *translated_size = 0; + *item_num = 0; + return; + } + + size_t size = 0; + size_t num = 0; + uint8_t *psrc = (uint8_t *)src; + rmt_item32_t *pdest = dest; + while (size < src_size && num < wanted_num) { + for (int i = 0; i < 8; i++) { + // MSB first + if (*psrc & (1 << (7 - i))) { + pdest->val = bitstream_high_low_1.val; + } else { + pdest->val = bitstream_high_low_0.val; + } + num++; + pdest++; + } + size++; + psrc++; + } + + *translated_size = size; + *item_num = num; +} + +// Use the reserved RMT channel to stream high/low data on the specified pin. +STATIC void machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len, uint8_t channel_id) { + rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel_id); + + // Use 40MHz clock (although 2MHz would probably be sufficient). + config.clk_div = 2; + + // Install the driver on this channel & pin. + check_esp_err(rmt_config(&config)); + check_esp_err(rmt_driver_install_core1(config.channel)); + + // Get the tick rate in kHz (this will likely be 40000). + uint32_t counter_clk_khz = 0; + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) + uint8_t div_cnt; + check_esp_err(rmt_get_clk_div(config.channel, &div_cnt)); + counter_clk_khz = APB_CLK_FREQ / div_cnt; + #else + check_esp_err(rmt_get_counter_clock(config.channel, &counter_clk_khz)); + #endif + + counter_clk_khz /= 1000; + + // Convert nanoseconds to pulse duration. + bitstream_high_low_0.duration0 = (counter_clk_khz * timing_ns[0]) / 1e6; + bitstream_high_low_0.duration1 = (counter_clk_khz * timing_ns[1]) / 1e6; + bitstream_high_low_1.duration0 = (counter_clk_khz * timing_ns[2]) / 1e6; + bitstream_high_low_1.duration1 = (counter_clk_khz * timing_ns[3]) / 1e6; + + // Install the bits->highlow translator. + rmt_translator_init(config.channel, bitstream_high_low_rmt_adapter); + + // Stream the byte data using the translator. + check_esp_err(rmt_write_sample(config.channel, buf, len, true)); + + // Wait 50% longer than we expect (if every bit takes the maximum time). + uint32_t timeout_ms = (3 * len / 2) * (1 + (8 * MAX(timing_ns[0] + timing_ns[1], timing_ns[2] + timing_ns[3])) / 1000); + check_esp_err(rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(timeout_ms))); + + // Uninstall the driver. + check_esp_err(rmt_driver_uninstall(config.channel)); + + // Cancel RMT output to GPIO pin. + gpio_matrix_out(pin, SIG_GPIO_OUT_IDX, false, false); +} + +/******************************************************************************/ +// Interface to machine.bitstream + +void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { + if (esp32_rmt_bitstream_channel_id < 0) { + machine_bitstream_high_low_bitbang(pin, timing_ns, buf, len); + } else { + machine_bitstream_high_low_rmt(pin, timing_ns, buf, len, esp32_rmt_bitstream_channel_id); + } +} + #endif // MICROPY_PY_MACHINE_BITSTREAM diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index 6c1e96879f..c0c7ddb705 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -84,8 +84,13 @@ STATIC const machine_pin_obj_t machine_pin_obj[] = { {{&machine_pin_type}, GPIO_NUM_13}, {{&machine_pin_type}, GPIO_NUM_14}, {{&machine_pin_type}, GPIO_NUM_15}, + #if CONFIG_ESP32_SPIRAM_SUPPORT + {{NULL}, -1}, + {{NULL}, -1}, + #else {{&machine_pin_type}, GPIO_NUM_16}, {{&machine_pin_type}, GPIO_NUM_17}, + #endif {{&machine_pin_type}, GPIO_NUM_18}, {{&machine_pin_type}, GPIO_NUM_19}, {{NULL}, -1}, @@ -172,7 +177,11 @@ STATIC const machine_pin_obj_t machine_pin_obj[] = { {{NULL}, -1}, // 23 not a pin {{NULL}, -1}, // 24 not a pin {{NULL}, -1}, // 25 not a pin - {{NULL}, -1}, // 26 FLASH/PSRAM + #if CONFIG_SPIRAM + {{NULL}, -1}, // 26 PSRAM + #else + {{&machine_pin_type}, GPIO_NUM_26}, + #endif {{NULL}, -1}, // 27 FLASH/PSRAM {{NULL}, -1}, // 28 FLASH/PSRAM {{NULL}, -1}, // 29 FLASH/PSRAM @@ -195,6 +204,13 @@ STATIC const machine_pin_obj_t machine_pin_obj[] = { {{&machine_pin_type}, GPIO_NUM_46}, #endif + + #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_ESP32S3_EXTENDED_IO + + {{&machine_pin_type}, GPIO_NUM_47}, + {{&machine_pin_type}, GPIO_NUM_48}, + + #endif }; // forward declaration @@ -518,8 +534,13 @@ STATIC const machine_pin_irq_obj_t machine_pin_irq_object[] = { {{&machine_pin_irq_type}, GPIO_NUM_13}, {{&machine_pin_irq_type}, GPIO_NUM_14}, {{&machine_pin_irq_type}, GPIO_NUM_15}, + #if CONFIG_ESP32_SPIRAM_SUPPORT + {{NULL}, -1}, + {{NULL}, -1}, + #else {{&machine_pin_irq_type}, GPIO_NUM_16}, {{&machine_pin_irq_type}, GPIO_NUM_17}, + #endif {{&machine_pin_irq_type}, GPIO_NUM_18}, {{&machine_pin_irq_type}, GPIO_NUM_19}, {{NULL}, -1}, @@ -601,7 +622,11 @@ STATIC const machine_pin_irq_obj_t machine_pin_irq_object[] = { {{NULL}, -1}, // 23 not a pin {{NULL}, -1}, // 24 not a pin {{NULL}, -1}, // 25 not a pin - {{NULL}, -1}, // 26 FLASH/PSRAM + #if CONFIG_SPIRAM + {{NULL}, -1}, // 26 PSRAM + #else + {{&machine_pin_irq_type}, GPIO_NUM_26}, + #endif {{NULL}, -1}, // 27 FLASH/PSRAM {{NULL}, -1}, // 28 FLASH/PSRAM {{NULL}, -1}, // 29 FLASH/PSRAM @@ -621,6 +646,14 @@ STATIC const machine_pin_irq_obj_t machine_pin_irq_object[] = { {{&machine_pin_irq_type}, GPIO_NUM_43}, {{&machine_pin_irq_type}, GPIO_NUM_44}, {{&machine_pin_irq_type}, GPIO_NUM_45}, + {{&machine_pin_irq_type}, GPIO_NUM_46}, + + #endif + + #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_ESP32S3_EXTENDED_IO + + {{&machine_pin_irq_type}, GPIO_NUM_47}, + {{&machine_pin_irq_type}, GPIO_NUM_48}, #endif }; diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index a7d7d29df8..43d44249dc 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -3,7 +3,10 @@ * * The MIT License (MIT) * - * Copyright (c) 2016 Damien P. George + * Copyright (c) 2016-2021 Damien P. George + * Copyright (c) 2018 Alan Dragomirecky + * Copyright (c) 2020 Antoine Aubert + * Copyright (c) 2021 Ihor Nehrutsa * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,186 +26,535 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include + +#include + +#include "py/runtime.h" +#include "py/mphal.h" + #include "driver/ledc.h" #include "esp_err.h" -#include "py/nlr.h" -#include "py/runtime.h" -#include "modmachine.h" -#include "mphalport.h" +#define PWM_DBG(...) +// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); -// Forward dec'l -extern const mp_obj_type_t machine_pwm_type; +// Total number of channels +#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX) -typedef struct _esp32_pwm_obj_t { - mp_obj_base_t base; +typedef struct _chan_t { + // Which channel has which GPIO pin assigned? + // (-1 if not assigned) gpio_num_t pin; - uint8_t active; - uint8_t channel; -} esp32_pwm_obj_t; + // Which channel has which timer assigned? + // (-1 if not assigned) + int timer_idx; +} chan_t; + +// List of PWM channels +STATIC chan_t chans[PWM_CHANNEL_MAX]; + +// channel_idx is an index (end-to-end sequential numbering) for all channels +// available on the chip and described in chans[] +#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel) +#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX) +#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX) + +// Total number of timers +#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX) -// Which channel has which GPIO pin assigned? -// (-1 if not assigned) -STATIC int chan_gpio[LEDC_CHANNEL_MAX]; +// List of timer configs +STATIC ledc_timer_config_t timers[PWM_TIMER_MAX]; + +// timer_idx is an index (end-to-end sequential numbering) for all timers +// available on the chip and configured in timers[] +#define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer) +#define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX) +#define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX) // Params for PW operation -// 5khz +// 5khz is default frequency #define PWFREQ (5000) -// High speed mode -#if CONFIG_IDF_TARGET_ESP32 -#define PWMODE (LEDC_HIGH_SPEED_MODE) -#else -#define PWMODE (LEDC_LOW_SPEED_MODE) -#endif + // 10-bit resolution (compatible with esp8266 PWM) #define PWRES (LEDC_TIMER_10_BIT) -// Timer 1 -#define PWTIMER (LEDC_TIMER_1) + +// Maximum duty value on 10-bit resolution +#define MAX_DUTY_U10 ((1 << PWRES) - 1) +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html#supported-range-of-frequency-and-duty-resolutions +// duty() uses 10-bit resolution or less +// duty_u16() and duty_ns() use 16-bit resolution or less + +// Possible highest resolution in device +#if (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT +#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) +#else +#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used +#endif +// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer +#define UI_RES_16_BIT (16) +// Maximum duty value on highest user interface resolution +#define UI_MAX_DUTY ((1 << UI_RES_16_BIT) - 1) +// How much to shift from the HIGHEST_PWM_RES duty resolution to the user interface duty resolution UI_RES_16_BIT +#define UI_RES_SHIFT (UI_RES_16_BIT - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3 + +// If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used +#define EMPIRIC_FREQ (10) // Hz // Config of timer upon which we run all PWM'ed GPIO pins STATIC bool pwm_inited = false; -STATIC ledc_timer_config_t timer_cfg = { - .duty_resolution = PWRES, - .freq_hz = PWFREQ, - .speed_mode = PWMODE, - .timer_num = PWTIMER -}; -STATIC void pwm_init(void) { +// MicroPython PWM object struct +typedef struct _machine_pwm_obj_t { + mp_obj_base_t base; + gpio_num_t pin; + bool active; + int mode; + int channel; + int timer; + int duty_x; // PWRES if duty(), HIGHEST_PWM_RES if duty_u16(), -HIGHEST_PWM_RES if duty_ns() + int duty_u10; // stored values from previous duty setters + int duty_u16; // - / - + int duty_ns; // - / - +} machine_pwm_obj_t; + +STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx); +STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty); +STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty); +STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns); +STATIC void pwm_init(void) { // Initial condition: no channels assigned - for (int x = 0; x < LEDC_CHANNEL_MAX; ++x) { - chan_gpio[x] = -1; + for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { + chans[i].pin = -1; + chans[i].timer_idx = -1; } - // Init with default timer params - ledc_timer_config(&timer_cfg); + // Prepare all timers config + // Initial condition: no timers assigned + for (int i = 0; i < PWM_TIMER_MAX; ++i) { + timers[i].duty_resolution = HIGHEST_PWM_RES; + // unset timer is -1 + timers[i].freq_hz = -1; + timers[i].speed_mode = TIMER_IDX_TO_MODE(i); + timers[i].timer_num = TIMER_IDX_TO_TIMER(i); + timers[i].clk_cfg = LEDC_AUTO_CLK; // will reinstall later according to the EMPIRIC_FREQ + } } -STATIC int set_freq(int newval) { - int ores = timer_cfg.duty_resolution; - int oval = timer_cfg.freq_hz; +// Deinit channel and timer if the timer is unused +STATIC void pwm_deinit(int channel_idx) { + // Valid channel? + if ((channel_idx >= 0) && (channel_idx < PWM_CHANNEL_MAX)) { + // Clean up timer if necessary + int timer_idx = chans[channel_idx].timer_idx; + if (timer_idx != -1) { + if (!is_timer_in_use(channel_idx, timer_idx)) { + check_esp_err(ledc_timer_rst(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx))); + // Flag it unused + timers[chans[channel_idx].timer_idx].freq_hz = -1; + } + } - // Find the highest bit resolution for the requested frequency - if (newval <= 0) { - newval = 1; + int pin = chans[channel_idx].pin; + if (pin != -1) { + int mode = CHANNEL_IDX_TO_MODE(channel_idx); + int channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); + // Mark it unused, and tell the hardware to stop routing + check_esp_err(ledc_stop(mode, channel, 0)); + // Disable ledc signal for the pin + // gpio_matrix_out(pin, SIG_GPIO_OUT_IDX, false, false); + if (mode == LEDC_LOW_SPEED_MODE) { + gpio_matrix_out(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true); + } else { + #if LEDC_SPEED_MODE_MAX > 1 + #if CONFIG_IDF_TARGET_ESP32 + gpio_matrix_out(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, true); + #else + #error Add supported CONFIG_IDF_TARGET_ESP32_xxx + #endif + #endif + } + } + chans[channel_idx].pin = -1; + chans[channel_idx].timer_idx = -1; } - unsigned int res = 0; - for (unsigned int i = LEDC_APB_CLK_HZ / newval; i > 1; i >>= 1, ++res) { +} + +// This called from Ctrl-D soft reboot +void machine_pwm_deinit_all(void) { + if (pwm_inited) { + for (int channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { + pwm_deinit(channel_idx); + } + pwm_inited = false; } - if (res == 0) { - res = 1; - } else if (res > PWRES) { - // Limit resolution to PWRES to match units of our duty - res = PWRES; +} + +STATIC void configure_channel(machine_pwm_obj_t *self) { + ledc_channel_config_t cfg = { + .channel = self->channel, + .duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2, + .gpio_num = self->pin, + .intr_type = LEDC_INTR_DISABLE, + .speed_mode = self->mode, + .timer_sel = self->timer, + }; + if (ledc_channel_config(&cfg) != ESP_OK) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin); } +} + +STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { + if (freq != timer->freq_hz) { + // Find the highest bit resolution for the requested frequency + unsigned int i = LEDC_APB_CLK_HZ; // 80 MHz + if (freq < EMPIRIC_FREQ) { + i = LEDC_REF_CLK_HZ; // 1 MHz + } + + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + // original code + i /= freq; + #else + // See https://github.com/espressif/esp-idf/issues/7722 + int divider = (i + freq / 2) / freq; // rounded + if (divider == 0) { + divider = 1; + } + float f = (float)i / divider; // actual frequency + if (f <= 1.0) { + f = 1.0; + } + i = (unsigned int)roundf((float)i / f); + #endif + + unsigned int res = 0; + for (; i > 1; i >>= 1) { + ++res; + } + if (res == 0) { + res = 1; + } else if (res > HIGHEST_PWM_RES) { + // Limit resolution to HIGHEST_PWM_RES to match units of our duty + res = HIGHEST_PWM_RES; + } + + // Configure the new resolution and frequency + unsigned int save_duty_resolution = timer->duty_resolution; + timer->duty_resolution = res; + timer->freq_hz = freq; + timer->clk_cfg = LEDC_USE_APB_CLK; + if (freq < EMPIRIC_FREQ) { + timer->clk_cfg = LEDC_USE_REF_TICK; + } + + // Set frequency + esp_err_t err = ledc_timer_config(timer); + if (err != ESP_OK) { + if (err == ESP_FAIL) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq); + } else { + check_esp_err(err); + } + } + // Reset the timer if low speed + if (self->mode == LEDC_LOW_SPEED_MODE) { + check_esp_err(ledc_timer_rst(self->mode, self->timer)); + } + + // Save the same duty cycle when frequency is changed + if (save_duty_resolution != timer->duty_resolution) { + if (self->duty_x == HIGHEST_PWM_RES) { + set_duty_u16(self, self->duty_u16); + } else if (self->duty_x == PWRES) { + set_duty_u10(self, self->duty_u10); + } else if (self->duty_x == -HIGHEST_PWM_RES) { + set_duty_ns(self, self->duty_ns); + } + } + } +} - // Configure the new resolution and frequency - timer_cfg.duty_resolution = res; - timer_cfg.freq_hz = newval; - if (ledc_timer_config(&timer_cfg) != ESP_OK) { - timer_cfg.duty_resolution = ores; - timer_cfg.freq_hz = oval; - return 0; +// Calculate the duty parameters based on an ns value +STATIC int ns_to_duty(machine_pwm_obj_t *self, int ns) { + ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; + int64_t duty = ((int64_t)ns * UI_MAX_DUTY * timer.freq_hz + 500000000LL) / 1000000000LL; + if ((ns > 0) && (duty == 0)) { + duty = 1; + } else if (duty > UI_MAX_DUTY) { + duty = UI_MAX_DUTY; } - return 1; + return duty; +} + +STATIC int duty_to_ns(machine_pwm_obj_t *self, int duty) { + ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; + int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY); + return ns; +} + +#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) + +STATIC uint32_t get_duty_u16(machine_pwm_obj_t *self) { + return ledc_get_duty(self->mode, self->channel) << (HIGHEST_PWM_RES + UI_RES_SHIFT - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution); +} + +STATIC uint32_t get_duty_u10(machine_pwm_obj_t *self) { + return get_duty_u16(self) >> (HIGHEST_PWM_RES - PWRES); +} + +STATIC uint32_t get_duty_ns(machine_pwm_obj_t *self) { + return duty_to_ns(self, get_duty_u16(self)); +} + +STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > UI_MAX_DUTY)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY); + } + ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; + int channel_duty = duty >> (HIGHEST_PWM_RES + UI_RES_SHIFT - timer.duty_resolution); + int max_duty = (1 << timer.duty_resolution) - 1; + if (channel_duty < 0) { + channel_duty = 0; + } else if (channel_duty > max_duty) { + channel_duty = max_duty; + } + check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); + + /* + // Bug: Sometimes duty is not set right now. + // Not a bug. It's a feature. The duty is applied at the beginning of the next signal period. + // Bug: It has been experimentally established that the duty is setted during 2 signal periods, but 1 period is expected. + // See https://github.com/espressif/esp-idf/issues/7288 + if (duty != get_duty_u16(self)) { + PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); + ets_delay_us(2 * 1000000 / timer.freq_hz); + if (duty != get_duty_u16(self)) { + PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); + } + } + */ + + self->duty_x = HIGHEST_PWM_RES; + self->duty_u16 = duty; +} + +STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > MAX_DUTY_U10)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_U10); + } + set_duty_u16(self, duty << (HIGHEST_PWM_RES + UI_RES_SHIFT - PWRES)); + self->duty_x = PWRES; + self->duty_u10 = duty; +} + +STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns) { + if ((ns < 0) || (ns > duty_to_ns(self, UI_MAX_DUTY))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI_MAX_DUTY)); + } + set_duty_u16(self, ns_to_duty(self, ns)); + self->duty_x = -HIGHEST_PWM_RES; + self->duty_ns = ns; } /******************************************************************************/ +#define SAME_FREQ_ONLY (true) +#define SAME_FREQ_OR_FREE (false) +#define ANY_MODE (-1) + +// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer +STATIC int find_timer(unsigned int freq, bool same_freq_only, int mode) { + int free_timer_idx_found = -1; + // Find a free PWM Timer using the same freq + for (int timer_idx = 0; timer_idx < PWM_TIMER_MAX; ++timer_idx) { + if ((mode == ANY_MODE) || (mode == TIMER_IDX_TO_MODE(timer_idx))) { + if (timers[timer_idx].freq_hz == freq) { + // A timer already uses the same freq. Use it now. + return timer_idx; + } + if (!same_freq_only && (free_timer_idx_found == -1) && (timers[timer_idx].freq_hz == -1)) { + free_timer_idx_found = timer_idx; + // Continue to check if a channel with the same freq is in use. + } + } + } + + return free_timer_idx_found; +} + +// Return true if the timer is in use in addition to current channel +STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx) { + for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { + if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) { + return true; + } + } + + return false; +} + +// Find a free PWM channel, also spot if our pin is already mentioned. +// Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel +STATIC int find_channel(int pin, int mode) { + int avail_idx = -1; + int channel_idx; + for (channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { + if ((mode == ANY_MODE) || (mode == CHANNEL_IDX_TO_MODE(channel_idx))) { + if (chans[channel_idx].pin == pin) { + break; + } + if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) { + avail_idx = channel_idx; + } + } + } + if (channel_idx >= PWM_CHANNEL_MAX) { + channel_idx = avail_idx; + } + return channel_idx; +} + +/******************************************************************************/ // MicroPython bindings for PWM -STATIC void esp32_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - esp32_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "PWM(%u", self->pin); +STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "PWM(Pin(%u)", self->pin); if (self->active) { - mp_printf(print, ", freq=%u, duty=%u", timer_cfg.freq_hz, - ledc_get_duty(PWMODE, self->channel)); + mp_printf(print, ", freq=%u", ledc_get_freq(self->mode, self->timer)); + + if (self->duty_x == PWRES) { + mp_printf(print, ", duty=%d", get_duty_u10(self)); + } else if (self->duty_x == -HIGHEST_PWM_RES) { + mp_printf(print, ", duty_ns=%d", get_duty_ns(self)); + } else { + mp_printf(print, ", duty_u16=%d", get_duty_u16(self)); + } + int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; + mp_printf(print, ", resolution=%d", resolution); + + mp_printf(print, ", (duty=%.2f%%, resolution=%.3f%%)", 100.0 * get_duty_raw(self) / (1 << resolution), 100.0 * 1 / (1 << resolution)); // percents + + mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); } mp_printf(print, ")"); } -STATIC void esp32_pwm_init_helper(esp32_pwm_obj_t *self, +// This called from pwm.init() method +STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_freq, ARG_duty }; + enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns }; static const mp_arg_t allowed_args[] = { { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_duty, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - int channel; - int avail = -1; + int channel_idx = find_channel(self->pin, ANY_MODE); + if (channel_idx == -1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes + } + + int duty = args[ARG_duty].u_int; + int duty_u16 = args[ARG_duty_u16].u_int; + int duty_ns = args[ARG_duty_ns].u_int; + if (((duty != -1) && (duty_u16 != -1)) || ((duty != -1) && (duty_ns != -1)) || ((duty_u16 != -1) && (duty_ns != -1))) { + mp_raise_ValueError(MP_ERROR_TEXT("only one of parameters 'duty', 'duty_u16' or 'duty_ns' is allowed")); + } - // Find a free PWM channel, also spot if our pin is - // already mentioned. - for (channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { - if (chan_gpio[channel] == self->pin) { - break; + int freq = args[ARG_freq].u_int; + // Check if freq wasn't passed as an argument + if (freq == -1) { + // Check if already set, otherwise use the default freq. + // It is possible in case: + // pwm = PWM(pin, freq=1000, duty=256) + // pwm = PWM(pin, duty=128) + if (chans[channel_idx].timer_idx != -1) { + freq = timers[chans[channel_idx].timer_idx].freq_hz; } - if ((avail == -1) && (chan_gpio[channel] == -1)) { - avail = channel; + if (freq <= 0) { + freq = PWFREQ; } } - if (channel >= LEDC_CHANNEL_MAX) { - if (avail == -1) { - mp_raise_ValueError(MP_ERROR_TEXT("out of PWM channels")); - } - channel = avail; + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("freqency must be from 1Hz to 40MHz")); } - self->channel = channel; - // New PWM assignment - self->active = 1; - if (chan_gpio[channel] == -1) { - ledc_channel_config_t cfg = { - .channel = channel, - .duty = (1 << timer_cfg.duty_resolution) / 2, - .gpio_num = self->pin, - .intr_type = LEDC_INTR_DISABLE, - .speed_mode = PWMODE, - .timer_sel = PWTIMER, - }; - if (ledc_channel_config(&cfg) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on pin %d"), self->pin); - } - chan_gpio[channel] = self->pin; + int timer_idx; + int current_timer_idx = chans[channel_idx].timer_idx; + bool current_in_use = is_timer_in_use(channel_idx, current_timer_idx); + if (current_in_use) { + timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx)); + } else { + timer_idx = chans[channel_idx].timer_idx; } - // Maybe change PWM timer - int tval = args[ARG_freq].u_int; - if (tval != -1) { - if (tval != timer_cfg.freq_hz) { - if (!set_freq(tval)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("bad frequency %d"), tval); - } + if (timer_idx == -1) { + timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, ANY_MODE); + } + if (timer_idx == -1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in all modes + } + + int mode = TIMER_IDX_TO_MODE(timer_idx); + if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { + // unregister old channel + chans[channel_idx].pin = -1; + chans[channel_idx].timer_idx = -1; + // find new channel + channel_idx = find_channel(self->pin, mode); + if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode } } + self->mode = mode; + self->timer = TIMER_IDX_TO_TIMER(timer_idx); + self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); + + // New PWM assignment + if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) { + configure_channel(self); + chans[channel_idx].pin = self->pin; + } + chans[channel_idx].timer_idx = timer_idx; + self->active = true; + + // Set timer frequency + set_freq(self, freq, &timers[timer_idx]); // Set duty cycle? - int dval = args[ARG_duty].u_int; - if (dval != -1) { - dval &= ((1 << PWRES) - 1); - dval >>= PWRES - timer_cfg.duty_resolution; - ledc_set_duty(PWMODE, channel, dval); - ledc_update_duty(PWMODE, channel); + if (duty_u16 != -1) { + set_duty_u16(self, duty_u16); + } else if (duty_ns != -1) { + set_duty_ns(self, duty_ns); + } else if (duty != -1) { + set_duty_u10(self, duty); + } else if (self->duty_x == 0) { + set_duty_u10(self, (1 << PWRES) / 2); // 50% } } -STATIC mp_obj_t esp32_pwm_make_new(const mp_obj_type_t *type, +// This called from PWM() constructor +STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + mp_arg_check_num(n_args, n_kw, 1, 2, true); gpio_num_t pin_id = machine_pin_get_id(args[0]); // create PWM object from the given pin - esp32_pwm_obj_t *self = m_new_obj(esp32_pwm_obj_t); + machine_pwm_obj_t *self = m_new_obj(machine_pwm_obj_t); self->base.type = &machine_pwm_type; self->pin = pin_id; - self->active = 0; + self->active = false; + self->mode = -1; self->channel = -1; + self->timer = -1; + self->duty_x = 0; // start the PWM subsystem if it's not already running if (!pwm_inited) { @@ -213,88 +565,96 @@ STATIC mp_obj_t esp32_pwm_make_new(const mp_obj_type_t *type, // start the PWM running for this channel mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - esp32_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); + mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); return MP_OBJ_FROM_PTR(self); } -STATIC mp_obj_t esp32_pwm_init(size_t n_args, - const mp_obj_t *args, mp_map_t *kw_args) { - esp32_pwm_init_helper(args[0], n_args - 1, args + 1, kw_args); - return mp_const_none; +// This called from pwm.deinit() method +STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { + int channel_idx = CHANNEL_IDX(self->mode, self->channel); + pwm_deinit(channel_idx); + self->active = false; + self->mode = -1; + self->channel = -1; + self->timer = -1; + self->duty_x = 0; } -MP_DEFINE_CONST_FUN_OBJ_KW(esp32_pwm_init_obj, 1, esp32_pwm_init); -STATIC mp_obj_t esp32_pwm_deinit(mp_obj_t self_in) { - esp32_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); - int chan = self->channel; +// Set's and get's methods of PWM class - // Valid channel? - if ((chan >= 0) && (chan < LEDC_CHANNEL_MAX)) { - // Mark it unused, and tell the hardware to stop routing - chan_gpio[chan] = -1; - ledc_stop(PWMODE, chan, 0); - self->active = 0; - self->channel = -1; - gpio_matrix_out(self->pin, SIG_GPIO_OUT_IDX, false, false); - } - return mp_const_none; +STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_pwm_deinit_obj, esp32_pwm_deinit); -STATIC mp_obj_t esp32_pwm_freq(size_t n_args, const mp_obj_t *args) { - if (n_args == 1) { - // get - return MP_OBJ_NEW_SMALL_INT(timer_cfg.freq_hz); +STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("freqency must be from 1Hz to 40MHz")); + } + if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) { + return; + } + + int current_timer_idx = chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx; + bool current_in_use = is_timer_in_use(CHANNEL_IDX(self->mode, self->channel), current_timer_idx); + + // Check if an already running timer with the same freq is running + int new_timer_idx = find_timer(freq, SAME_FREQ_ONLY, self->mode); + + // If no existing timer was found, and the current one is in use, then find a new one + if ((new_timer_idx == -1) && current_in_use) { + // Have to find a new timer + new_timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, self->mode); + + if (new_timer_idx == -1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in current mode + } } - // set - int tval = mp_obj_get_int(args[1]); - if (!set_freq(tval)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("bad frequency %d"), tval); + if ((new_timer_idx != -1) && (new_timer_idx != current_timer_idx)) { + // Bind the channel to the new timer + chans[self->channel].timer_idx = new_timer_idx; + + if (ledc_bind_channel_timer(self->mode, self->channel, TIMER_IDX_TO_TIMER(new_timer_idx)) != ESP_OK) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel")); + } + + if (!current_in_use) { + // Free the old timer + check_esp_err(ledc_timer_rst(self->mode, self->timer)); + // Flag it unused + timers[current_timer_idx].freq_hz = -1; + } + + current_timer_idx = new_timer_idx; } - return mp_const_none; + self->mode = TIMER_IDX_TO_MODE(current_timer_idx); + self->timer = TIMER_IDX_TO_TIMER(current_timer_idx); + + // Set the frequency + set_freq(self, freq, &timers[current_timer_idx]); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_pwm_freq_obj, 1, 2, esp32_pwm_freq); +STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(get_duty_u10(self)); +} -STATIC mp_obj_t esp32_pwm_duty(size_t n_args, const mp_obj_t *args) { - esp32_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]); - int duty; +STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) { + set_duty_u10(self, duty); +} - if (n_args == 1) { - // get - duty = ledc_get_duty(PWMODE, self->channel); - duty <<= PWRES - timer_cfg.duty_resolution; - return MP_OBJ_NEW_SMALL_INT(duty); - } +STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(get_duty_u16(self)); +} + +STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) { + set_duty_u16(self, duty_u16); +} - // set - duty = mp_obj_get_int(args[1]); - duty &= ((1 << PWRES) - 1); - duty >>= PWRES - timer_cfg.duty_resolution; - ledc_set_duty(PWMODE, self->channel, duty); - ledc_update_duty(PWMODE, self->channel); +STATIC mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(get_duty_ns(self)); +} - return mp_const_none; +STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) { + set_duty_ns(self, duty_ns); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_pwm_duty_obj, - 1, 2, esp32_pwm_duty); - -STATIC const mp_rom_map_elem_t esp32_pwm_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp32_pwm_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_pwm_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&esp32_pwm_freq_obj) }, - { MP_ROM_QSTR(MP_QSTR_duty), MP_ROM_PTR(&esp32_pwm_duty_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(esp32_pwm_locals_dict, - esp32_pwm_locals_dict_table); - -const mp_obj_type_t machine_pwm_type = { - { &mp_type_type }, - .name = MP_QSTR_PWM, - .print = esp32_pwm_print, - .make_new = esp32_pwm_make_new, - .locals_dict = (mp_obj_dict_t *)&esp32_pwm_locals_dict, -}; diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 6726f3d3ef..654edb042d 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -26,15 +26,17 @@ * THE SOFTWARE. */ -#include #include #include +#include -#include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "esp_system.h" #include "nvs_flash.h" +#include "esp_task.h" #include "soc/cpu.h" +#include "esp_log.h" #if CONFIG_IDF_TARGET_ESP32 #include "esp32/spiram.h" @@ -44,50 +46,30 @@ #include "esp32s3/spiram.h" #endif -#include -#include - -#include "board_config.h" -#include "esp_log.h" -#include "esp_partition.h" -#include "esp_system.h" -#include "esp_task.h" -#include "esp_vfs.h" -#include "esp_vfs_fat.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "modmachine.h" -#include "modnetwork.h" -#include "mpthreadport.h" -#include "py/compile.h" -#include "py/gc.h" -#include "py/mphal.h" +#include "py/stackctrl.h" #include "py/nlr.h" +#include "py/compile.h" +#include "py/runtime.h" #include "py/persistentcode.h" #include "py/repl.h" -#include "py/runtime.h" -#include "py/stackctrl.h" +#include "py/gc.h" +#include "py/mphal.h" #include "shared/readline/readline.h" #include "shared/runtime/pyexec.h" #include "uart.h" #include "usb.h" #include "usb_serial_jtag.h" -#include "wear_levelling.h" +#include "modmachine.h" +#include "modnetwork.h" +#include "mpthreadport.h" + #if MICROPY_BLUETOOTH_NIMBLE #include "extmod/modbluetooth.h" #endif -#include "extmod/vfs.h" -#if MICROPY_VFS_POSIX -#include "extmod/vfs_posix.h" -#endif -#if AOS_COMP_KV -#include "aos/kv.h" -#endif - // MicroPython runs as a task under FreeRTOS -#define MP_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) -#define MP_TASK_STACK_SIZE (16 * 1024) +#define MP_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) +#define MP_TASK_STACK_SIZE (16 * 1024) // Set the margin for detecting stack overflow, depending on the CPU architecture. #if CONFIG_IDF_TARGET_ESP32C3 @@ -96,100 +78,49 @@ #define MP_TASK_STACK_LIMIT_MARGIN (1024) #endif -int vprintf_null(const char *format, va_list ap) -{ +int vprintf_null(const char *format, va_list ap) { // do nothing: this is used as a log target during raw repl mode return 0; } -// Try to mount the data on "/data" and chdir to it for the boot-up directory. -static int32_t mount_fs(char *mount_point_str) -{ -#if MICROPY_VFS_POSIX - mp_obj_t mount_point = mp_obj_new_str(mount_point_str, strlen(mount_point_str)); - mp_obj_t bdev = mp_type_vfs_posix.make_new(&mp_type_vfs_posix, 0, 0, NULL); - int32_t ret = mp_vfs_mount_and_chdir_protected(bdev, mount_point); - if (ret != 0) { - printf("mount_fs failed with mount_point: %s\n", mount_point_str); - return -1; - } -#else - (void)mount_point_str; -#endif - return 0; -} - - -static char *is_mainpy_exist() -{ - // check whether main/pyamp/main.py - FILE *fd = fopen(AMP_PY_ENTRY_HAAS, "r"); - if (fd != NULL) { - printf(" ==== python file check %s ====\n", AMP_PY_ENTRY_HAAS); - fclose(fd); - return AMP_PY_ENTRY_HAAS; - } - - fd = fopen(AMP_PY_ENTRY_ROOT, "r"); - if (fd != NULL) { - printf(" ==== python file check %s ====\n", AMP_PY_ENTRY_ROOT); - fclose(fd); - return AMP_PY_ENTRY_ROOT; - } - - return NULL; -} - -void mp_task(void *pvParameter) -{ +void mp_task(void *pvParameter) { volatile uint32_t sp = (uint32_t)get_sp(); - int ret; -#if MICROPY_PY_THREAD + #if MICROPY_PY_THREAD mp_thread_init(pxTaskGetStackStart(NULL), MP_TASK_STACK_SIZE / sizeof(uintptr_t)); -#endif -#if CONFIG_USB_ENABLED + #endif + #if CONFIG_USB_ENABLED usb_init(); -#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG + #elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG usb_serial_jtag_init(); -#else + #else uart_init(); -#endif + #endif machine_init(); -// TODO: CONFIG_SPIRAM_SUPPORT is for 3.3 compatibility, remove after move to 4.0. -#if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_SPIRAM_SUPPORT + // TODO: CONFIG_SPIRAM_SUPPORT is for 3.3 compatibility, remove after move to 4.0. + #if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_SPIRAM_SUPPORT // Try to use the entire external SPIRAM directly for the heap size_t mp_task_heap_size; - void *mp_task_heap = (void *)0x3f800000; + void *mp_task_heap = (void *)SOC_EXTRAM_DATA_LOW; switch (esp_spiram_get_chip_size()) { - case ESP_SPIRAM_SIZE_16MBITS: -#if CONFIG_SPIRAM_USE_MALLOC - mp_task_heap_size = 512 * 1024; - mp_task_heap = malloc(mp_task_heap_size); -#else - mp_task_heap_size = 2 * 1024 * 1024; -#endif - break; - case ESP_SPIRAM_SIZE_32MBITS: - case ESP_SPIRAM_SIZE_64MBITS: -#if CONFIG_SPIRAM_USE_MALLOC - mp_task_heap_size = 1 * 1024 * 1024; - mp_task_heap = malloc(mp_task_heap_size); -#else - mp_task_heap_size = 4 * 1024 * 1024; -#endif - break; - default: - // No SPIRAM, fallback to normal allocation - mp_task_heap_size = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); - mp_task_heap = malloc(mp_task_heap_size); - break; + case ESP_SPIRAM_SIZE_16MBITS: + mp_task_heap_size = 2 * 1024 * 1024; + break; + case ESP_SPIRAM_SIZE_32MBITS: + case ESP_SPIRAM_SIZE_64MBITS: + mp_task_heap_size = 4 * 1024 * 1024; + break; + default: + // No SPIRAM, fallback to normal allocation + mp_task_heap_size = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); + mp_task_heap = malloc(mp_task_heap_size); + break; } -#elif CONFIG_ESP32S2_SPIRAM_SUPPORT || CONFIG_ESP32S3_SPIRAM_SUPPORT + #elif CONFIG_ESP32S2_SPIRAM_SUPPORT || CONFIG_ESP32S3_SPIRAM_SUPPORT // Try to use the entire external SPIRAM directly for the heap size_t mp_task_heap_size; size_t esp_spiram_size = esp_spiram_get_size(); - void *mp_task_heap = (void *)0x3ff80000 - esp_spiram_size; + void *mp_task_heap = (void *)SOC_EXTRAM_DATA_HIGH - esp_spiram_size; if (esp_spiram_size > 0) { mp_task_heap_size = esp_spiram_size; } else { @@ -197,12 +128,12 @@ void mp_task(void *pvParameter) mp_task_heap_size = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); mp_task_heap = malloc(mp_task_heap_size); } -#else + #else // Allocate the uPy heap using malloc and get the largest available region size_t mp_task_heap_size = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); - mp_task_heap_size = 96 * 1024; + printf("gc_heap_size is : %d \n", mp_task_heap_size); void *mp_task_heap = malloc(mp_task_heap_size); -#endif + #endif soft_reset: // initialise the stack pointer for the main thread @@ -210,52 +141,25 @@ void mp_task(void *pvParameter) mp_stack_set_limit(MP_TASK_STACK_SIZE - MP_TASK_STACK_LIMIT_MARGIN); gc_init(mp_task_heap, mp_task_heap + mp_task_heap_size); mp_init(); - mp_obj_list_init(mp_sys_path, 0); - mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_lib)); - // add /data/pyamp to system path - char *path = MP_FS_ROOT_DIR "/pyamp"; - mp_obj_list_append(mp_sys_path, mp_obj_new_str_via_qstr(path, strlen(path))); - // add /data/lib/ to system path - path = MP_FS_ROOT_DIR "/lib"; - mp_obj_list_append(mp_sys_path, mp_obj_new_str_via_qstr(path, strlen(path))); - - mp_obj_list_init(mp_sys_argv, 0); -#if 1 - printf("mount fs\r\n"); - ret = mount_fs("/"); - if (ret != 0) { - printf(" !!!!!!!! %s, %d, faild to mount fs !!!!!!!!\n", __func__, __LINE__); - } - MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table); -#endif - readline_init0(); // initialise peripherals machine_pins_init(); -#if MICROPY_PY_MACHINE_I2S + #if MICROPY_PY_MACHINE_I2S machine_i2s_init0(); -#endif + #endif // run boot-up scripts - // pyexec_frozen_module("_boot.py"); - // do not delete this message, haas studio need use this message judge python status - printf(" ==== python execute bootpy ====\n"); + pyexec_frozen_module("_boot.py"); pyexec_file_if_exists("boot.py"); if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { - char *path = is_mainpy_exist(); - if (path != NULL) { - // do not delete this message, haas studio need use this message judge python status - printf(" ==== python execute from %s ====\n", path); - int ret = pyexec_file_if_exists(path); - if (ret & PYEXEC_FORCED_EXIT) { - goto soft_reset_exit; - } + int ret = pyexec_file_if_exists("main.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; } } - uint32_t esp_get_free_heap_size(void); - printf("%s-%d %d\r\n", __func__, __LINE__, esp_get_free_heap_size()); + for (;;) { if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { vprintf_like_t vprintf_log = esp_log_set_vprintf(vprintf_null); @@ -272,21 +176,23 @@ void mp_task(void *pvParameter) soft_reset_exit: -#if MICROPY_BLUETOOTH_NIMBLE + #if MICROPY_BLUETOOTH_NIMBLE mp_bluetooth_deinit(); -#endif + #endif machine_timer_deinit_all(); -#if MICROPY_PY_THREAD + #if MICROPY_PY_THREAD mp_thread_deinit(); -#endif + #endif gc_sweep_all(); mp_hal_stdout_tx_str("MPY: soft reboot\r\n"); // deinitialise peripherals + machine_pwm_deinit_all(); + // TODO: machine_rmt_deinit_all(); machine_pins_deinit(); machine_deinit(); usocket_events_deinit(); @@ -296,68 +202,34 @@ void mp_task(void *pvParameter) goto soft_reset; } - -static void queue_handler_task(void *p) -{ - py_task_init(); - while (1) { - /* loop for asynchronous operation */ - if (py_task_yield(200) == 1) { - printf("pyengine task yield exit! \r\n"); - break; - } - } -} - -void app_main(void) -{ +void boardctrl_startup(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { nvs_flash_erase(); nvs_flash_init(); } +} -#if AOS_COMP_KV - ret = aos_kv_init(); - if (ret) { - printf("kv init fail . ret is %d\r\n", ret); - } -#endif - - ulog_init(); - - littlefs_register(); +void app_main(void) { + // Hook for a board to run code at start up. + // This defaults to initialising NVS. + MICROPY_BOARD_STARTUP(); - TaskHandle_t mp_queue_task_handle; - ret = xTaskCreatePinnedToCore(queue_handler_task, "queue_handler", 1024 * 8 / sizeof(StackType_t), NULL, - ESP_TASK_PRIO_MIN + 3, &mp_queue_task_handle, MP_TASK_COREID); - if (ret != pdPASS) { - printf("queue_handler_task creat failed!\r\n"); - return; - } - - ret = xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, - &mp_main_task_handle, MP_TASK_COREID); - if (ret != pdPASS) { - printf("mp_task task creat failed!\r\n"); - return; - } + // Create and transfer control to the MicroPython task. + xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); } -void nlr_jump_fail(void *val) -{ +void nlr_jump_fail(void *val) { printf("NLR jump failed, val=%p\n", val); esp_restart(); } // modussl_mbedtls uses this function but it's not enabled in ESP IDF -void mbedtls_debug_set_threshold(int threshold) -{ +void mbedtls_debug_set_threshold(int threshold) { (void)threshold; } -void *esp_native_code_commit(void *buf, size_t len, void *reloc) -{ +void *esp_native_code_commit(void *buf, size_t len, void *reloc) { len = (len + 3) & ~3; uint32_t *p = heap_caps_malloc(len, MALLOC_CAP_EXEC); if (p == NULL) { diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt index 5c409d5825..68a4815d7e 100644 --- a/ports/esp32/main/CMakeLists.txt +++ b/ports/esp32/main/CMakeLists.txt @@ -1,21 +1,14 @@ # Set location of base MicroPython directory. if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../../engine ABSOLUTE) + get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../.. ABSOLUTE) endif() # Include core source components. -include(${MICROPY_DIR}/../modules/portmod.cmake) -include(${MICROPY_DIR}/../external/external.cmake) - -# Include port extmod component. include(${MICROPY_DIR}/py/py.cmake) if(NOT CMAKE_BUILD_EARLY_EXPANSION) include(${MICROPY_DIR}/py/usermod.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) - if(MICROPY_PY_LVGL) - include(${MICROPY_DIR}/lib/lv_bindings/mkrules.cmake) - endif() endif() set(MICROPY_QSTRDEFS_PORT @@ -51,6 +44,7 @@ set(MICROPY_SOURCE_DRIVERS ) set(MICROPY_SOURCE_PORT + ${PROJECT_DIR}/main.c ${PROJECT_DIR}/uart.c ${PROJECT_DIR}/usb.c ${PROJECT_DIR}/usb_serial_jtag.c @@ -65,16 +59,15 @@ set(MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_pin.c ${PROJECT_DIR}/machine_touchpad.c ${PROJECT_DIR}/machine_adc.c - ${PROJECT_DIR}/machine_adcblock.c ${PROJECT_DIR}/machine_dac.c ${PROJECT_DIR}/machine_i2c.c ${PROJECT_DIR}/machine_i2s.c - ${PROJECT_DIR}/machine_pwm.c ${PROJECT_DIR}/machine_uart.c ${PROJECT_DIR}/modmachine.c ${PROJECT_DIR}/modnetwork.c ${PROJECT_DIR}/network_lan.c ${PROJECT_DIR}/network_ppp.c + ${PROJECT_DIR}/network_wlan.c ${PROJECT_DIR}/mpnimbleport.c ${PROJECT_DIR}/modsocket.c ${PROJECT_DIR}/modesp.c @@ -88,7 +81,6 @@ set(MICROPY_SOURCE_PORT ${PROJECT_DIR}/mpthreadport.c ${PROJECT_DIR}/machine_rtc.c ${PROJECT_DIR}/machine_sdcard.c - ${PROJECT_DIR}/utility.c ) set(MICROPY_SOURCE_QSTR @@ -98,9 +90,7 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_SHARED} ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_PORT} - ${MICROPY_SOURCE_MODULES_PORT} - ${MICROPY_SOURCE_EXTERNAL} - ${LV_SRC} + ${MICROPY_SOURCE_BOARD} ) set(IDF_COMPONENTS @@ -108,16 +98,12 @@ set(IDF_COMPONENTS bootloader_support bt driver - esp_adc_cal esp_common esp_eth esp_event - esp_http_client esp_ringbuf esp_rom - esp-tls esp_wifi -#fatfs freertos heap log @@ -125,7 +111,6 @@ set(IDF_COMPONENTS mbedtls mdns newlib - nghttp nvs_flash sdmmc soc @@ -133,7 +118,6 @@ set(IDF_COMPONENTS tcpip_adapter ulp vfs - wear_levelling xtensa ) @@ -174,18 +158,13 @@ idf_component_register( ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_DRIVERS} ${MICROPY_SOURCE_PORT} - ${MICROPY_SOURCE_MODULES_PORT} - ${MICROPY_SOURCE_EXTERNAL} - ${LV_SRC} + ${MICROPY_SOURCE_BOARD} INCLUDE_DIRS ${MICROPY_INC_CORE} ${MICROPY_INC_USERMOD} ${MICROPY_PORT_DIR} ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} - ${MICROPY_INC_MODULES_PORT} - ${MICROPY_INC_EXTERNAL} - ${LV_INCLUDE} REQUIRES ${IDF_COMPONENTS} ) @@ -198,14 +177,13 @@ set(MICROPY_CROSS_FLAGS -march=xtensawin) # Set compile options for this port. target_compile_definitions(${MICROPY_TARGET} PUBLIC - ${MICROPY_DEF_MODULES_PORT} ${MICROPY_DEF_CORE} MICROPY_ESP_IDF_4=1 - ESP_PLATFORM=1 + MICROPY_VFS_FAT=1 + MICROPY_VFS_LFS2=1 FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\" LFS1_NO_MALLOC LFS1_NO_DEBUG LFS1_NO_WARN LFS1_NO_ERROR LFS1_NO_ASSERT LFS2_NO_MALLOC LFS2_NO_DEBUG LFS2_NO_WARN LFS2_NO_ERROR LFS2_NO_ASSERT - LV_KCONFIG_IGNORE ) # Disable some warnings to keep the build output clean. @@ -213,23 +191,19 @@ target_compile_options(${MICROPY_TARGET} PUBLIC -Wno-clobbered -Wno-deprecated-declarations -Wno-missing-field-initializers - -Wno-implicit-function-declaration - -Wno-unused-const-variable - -Wno-format-truncation - -Wno-pointer-sign - -Wno-unused-local-typedefs ) # Add additional extmod and usermod components. target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) target_link_libraries(${MICROPY_TARGET} usermod) + # Collect all of the include directories and compile definitions for the IDF components. foreach(comp ${IDF_COMPONENTS}) micropy_gather_target_properties(__idf_${comp}) endforeach() -if(IDF_VERSION_MINOR GREATER_EQUAL 2) +if(IDF_VERSION_MINOR GREATER_EQUAL 2 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) # These paths cannot currently be found by the IDF_COMPONENTS search loop above, # so add them explicitly. list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/${IDF_TARGET}/include) @@ -242,8 +216,3 @@ endif() # Include the main MicroPython cmake rules. include(${MICROPY_DIR}/py/mkrules.cmake) - -if(MICROPY_PY_LVGL) - # Add lv_bindings rules - all_lv_bindings() -endif() diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index 18bd62ee41..d76b3a49a2 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -26,9 +26,13 @@ #define RTC_LAST_EXT_PIN 39 #define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS) +extern int8_t esp32_rmt_bitstream_channel_id; + extern const mp_obj_type_t esp32_nvs_type; extern const mp_obj_type_t esp32_partition_type; extern const mp_obj_type_t esp32_rmt_type; extern const mp_obj_type_t esp32_ulp_type; +esp_err_t rmt_driver_install_core1(uint8_t channel_id); + #endif // MICROPY_INCLUDED_ESP32_MODESP32_H diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index cae8b4534b..cca2015729 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -53,6 +53,7 @@ #include "extmod/machine_mem.h" #include "extmod/machine_signal.h" #include "extmod/machine_pulse.h" +#include "extmod/machine_pwm.h" #include "extmod/machine_i2c.h" #include "extmod/machine_spi.h" #include "modmachine.h" @@ -296,7 +297,6 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, #endif { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, - { MP_ROM_QSTR(MP_QSTR_ADCBlock), MP_ROM_PTR(&machine_adcblock_type) }, #if MICROPY_PY_MACHINE_DAC { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, #endif diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index e0eb3729ab..c773f8dbc9 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -14,9 +14,7 @@ extern const mp_obj_type_t machine_wdt_type; extern const mp_obj_type_t machine_pin_type; extern const mp_obj_type_t machine_touchpad_type; extern const mp_obj_type_t machine_adc_type; -extern const mp_obj_type_t machine_adcblock_type; extern const mp_obj_type_t machine_dac_type; -extern const mp_obj_type_t machine_pwm_type; extern const mp_obj_type_t machine_hw_i2c_type; extern const mp_obj_type_t machine_hw_spi_type; extern const mp_obj_type_t machine_i2s_type; @@ -28,6 +26,8 @@ void machine_init(void); void machine_deinit(void); void machine_pins_init(void); void machine_pins_deinit(void); +void machine_pwm_deinit_all(void); +// TODO: void machine_rmt_deinit_all(void); void machine_timer_deinit_all(void); void machine_i2s_init0(); diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 6b89d51dc9..a16c67eeb9 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -31,28 +31,16 @@ * THE SOFTWARE. */ -#include -#include #include -#include "py/nlr.h" -#include "py/objlist.h" #include "py/runtime.h" -#include "py/mphal.h" #include "py/mperrno.h" #include "shared/netutils/netutils.h" -#include "esp_eth.h" +#include "modnetwork.h" + #include "esp_wifi.h" #include "esp_log.h" #include "lwip/dns.h" -#include "mdns.h" - -#if !MICROPY_ESP_IDF_4 -#include "esp_wifi_types.h" -#include "esp_event_loop.h" -#endif - -#include "modnetwork.h" #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) #define DNS_MAIN TCPIP_ADAPTER_DNS_MAIN @@ -62,7 +50,7 @@ #define MODNETWORK_INCLUDE_CONSTANTS (1) -NORETURN void _esp_exceptions(esp_err_t e) { +NORETURN void esp_exceptions_helper(esp_err_t e) { switch (e) { case ESP_ERR_WIFI_NOT_INIT: mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Initialized")); @@ -107,137 +95,16 @@ NORETURN void _esp_exceptions(esp_err_t e) { } } -static inline void esp_exceptions(esp_err_t e) { - if (e != ESP_OK) { - _esp_exceptions(e); - } -} - -#define ESP_EXCEPTIONS(x) do { esp_exceptions(x); } while (0); - -typedef struct _wlan_if_obj_t { - mp_obj_base_t base; - int if_id; -} wlan_if_obj_t; - -const mp_obj_type_t wlan_if_type; -STATIC const wlan_if_obj_t wlan_sta_obj = {{&wlan_if_type}, WIFI_IF_STA}; -STATIC const wlan_if_obj_t wlan_ap_obj = {{&wlan_if_type}, WIFI_IF_AP}; - -// Set to "true" if esp_wifi_start() was called -static bool wifi_started = false; - -// Set to "true" if the STA interface is requested to be connected by the -// user, used for automatic reassociation. -static bool wifi_sta_connect_requested = false; - -// Set to "true" if the STA interface is connected to wifi and has IP address. -static bool wifi_sta_connected = false; - -// Store the current status. 0 means None here, safe to do so as first enum value is WIFI_REASON_UNSPECIFIED=1. -static uint8_t wifi_sta_disconn_reason = 0; - -#if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER -// Whether mDNS has been initialised or not -static bool mdns_initialised = false; -#endif - -static uint8_t conf_wifi_sta_reconnects = 0; -static uint8_t wifi_sta_reconnects; -#if MICROPY_PY_CHANNEL_ENABLE -char _amp_ssid[64] = { 0 }; -char _amp_password[64] = { 0 }; - -#endif // This function is called by the system-event task and so runs in a different // thread to the main MicroPython task. It must not raise any Python exceptions. static esp_err_t event_handler(void *ctx, system_event_t *event) { switch (event->event_id) { case SYSTEM_EVENT_STA_START: - ESP_LOGI("wifi", "STA_START"); - wifi_sta_reconnects = 0; - break; case SYSTEM_EVENT_STA_CONNECTED: - ESP_LOGI("network", "CONNECTED"); - break; case SYSTEM_EVENT_STA_GOT_IP: - ESP_LOGI("network", "GOT_IP"); - wifi_sta_connected = true; - wifi_sta_disconn_reason = 0; // Success so clear error. (in case of new error will be replaced anyway) - extern int activation_report(void); - activation_report(); // report HaaS activation info once. - #if MICROPY_PY_CHANNEL_ENABLE - extern int check_channel_enable(void); - extern int save_ssid_and_password(char *ssid, char *passwd); - extern int on_get_url(char *url); - save_ssid_and_password(_amp_ssid, _amp_password); - if (check_channel_enable() == 0) { - amp_otaput_init(on_get_url); - } - #endif - #if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER - if (!mdns_initialised) { - mdns_init(); - #if MICROPY_HW_ENABLE_MDNS_RESPONDER - const char *hostname = NULL; - if (tcpip_adapter_get_hostname(WIFI_IF_STA, &hostname) != ESP_OK || hostname == NULL) { - hostname = "esp32"; - } - mdns_hostname_set(hostname); - mdns_instance_name_set(hostname); - #endif - mdns_initialised = true; - } - #endif - break; - case SYSTEM_EVENT_STA_DISCONNECTED: { - // This is a workaround as ESP32 WiFi libs don't currently - // auto-reassociate. - system_event_sta_disconnected_t *disconn = &event->event_info.disconnected; - char *message = ""; - wifi_sta_disconn_reason = disconn->reason; - switch (disconn->reason) { - case WIFI_REASON_BEACON_TIMEOUT: - // AP has dropped out; try to reconnect. - message = "\nbeacon timeout"; - break; - case WIFI_REASON_NO_AP_FOUND: - // AP may not exist, or it may have momentarily dropped out; try to reconnect. - message = "\nno AP found"; - break; - case WIFI_REASON_AUTH_FAIL: - // Password may be wrong, or it just failed to connect; try to reconnect. - message = "\nauthentication failed"; - break; - default: - // Let other errors through and try to reconnect. - break; - } - ESP_LOGI("wifi", "STA_DISCONNECTED, reason:%d%s", disconn->reason, message); - - wifi_sta_connected = false; - if (wifi_sta_connect_requested) { - wifi_mode_t mode; - if (esp_wifi_get_mode(&mode) != ESP_OK) { - break; - } - if (!(mode & WIFI_MODE_STA)) { - break; - } - if (conf_wifi_sta_reconnects) { - ESP_LOGI("wifi", "reconnect counter=%d, max=%d", - wifi_sta_reconnects, conf_wifi_sta_reconnects); - if (++wifi_sta_reconnects >= conf_wifi_sta_reconnects) { - break; - } - } - esp_err_t e = esp_wifi_connect(); - if (e != ESP_OK) { - ESP_LOGI("wifi", "error attempting to reconnect: 0x%04x", e); - } - } + case SYSTEM_EVENT_STA_DISCONNECTED: + network_wlan_event_handler(event); break; - } case SYSTEM_EVENT_GOT_IP6: ESP_LOGI("network", "Got IPv6"); break; @@ -263,49 +130,13 @@ static esp_err_t event_handler(void *ctx, system_event_t *event) { return ESP_OK; } -/*void error_check(bool status, const char *msg) { - if (!status) { - mp_raise_msg(&mp_type_OSError, msg); - } -} -*/ - -STATIC void require_if(mp_obj_t wlan_if, int if_no) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(wlan_if); - if (self->if_id != if_no) { - mp_raise_msg(&mp_type_OSError, if_no == WIFI_IF_STA ? MP_ERROR_TEXT("STA required") : MP_ERROR_TEXT("AP required")); - } -} - -STATIC mp_obj_t get_wlan(size_t n_args, const mp_obj_t *args) { - static int initialized = 0; - if (!initialized) { - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_LOGD("modnetwork", "Initializing WiFi"); - ESP_EXCEPTIONS(esp_wifi_init(&cfg)); - ESP_EXCEPTIONS(esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_LOGD("modnetwork", "Initialized"); - initialized = 1; - } - - int idx = (n_args > 0) ? mp_obj_get_int(args[0]) : WIFI_IF_STA; - if (idx == WIFI_IF_STA) { - return MP_OBJ_FROM_PTR(&wlan_sta_obj); - } else if (idx == WIFI_IF_AP) { - return MP_OBJ_FROM_PTR(&wlan_ap_obj); - } else { - mp_raise_ValueError(MP_ERROR_TEXT("invalid WLAN interface identifier")); - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(get_wlan_obj, 0, 1, get_wlan); - STATIC mp_obj_t esp_initialize() { static int initialized = 0; if (!initialized) { ESP_LOGD("modnetwork", "Initializing TCP/IP"); tcpip_adapter_init(); ESP_LOGD("modnetwork", "Initializing Event Loop"); - ESP_EXCEPTIONS(esp_event_loop_init(event_handler, NULL)); + esp_exceptions(esp_event_loop_init(event_handler, NULL)); ESP_LOGD("modnetwork", "esp_event_loop_init done"); initialized = 1; } @@ -313,221 +144,6 @@ STATIC mp_obj_t esp_initialize() { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_initialize_obj, esp_initialize); -#if (WIFI_MODE_STA & WIFI_MODE_AP != WIFI_MODE_NULL || WIFI_MODE_STA | WIFI_MODE_AP != WIFI_MODE_APSTA) -#error WIFI_MODE_STA and WIFI_MODE_AP are supposed to be bitfields! -#endif - -STATIC mp_obj_t esp_active(size_t n_args, const mp_obj_t *args) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - wifi_mode_t mode; - if (!wifi_started) { - mode = WIFI_MODE_NULL; - } else { - ESP_EXCEPTIONS(esp_wifi_get_mode(&mode)); - } - - int bit = (self->if_id == WIFI_IF_STA) ? WIFI_MODE_STA : WIFI_MODE_AP; - - if (n_args > 1) { - bool active = mp_obj_is_true(args[1]); - mode = active ? (mode | bit) : (mode & ~bit); - if (mode == WIFI_MODE_NULL) { - if (wifi_started) { - ESP_EXCEPTIONS(esp_wifi_stop()); - wifi_started = false; - } - } else { - ESP_EXCEPTIONS(esp_wifi_set_mode(mode)); - if (!wifi_started) { - ESP_EXCEPTIONS(esp_wifi_start()); - wifi_started = true; - } - } - } - - return (mode & bit) ? mp_const_true : mp_const_false; -} - -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_active_obj, 1, 2, esp_active); - -STATIC mp_obj_t esp_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_ssid, ARG_password, ARG_bssid }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - }; - - // parse args - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - wifi_config_t wifi_sta_config = {0}; - - // configure any parameters that are given - if (n_args > 1) { - size_t len; - const char *p; - if (args[ARG_ssid].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_ssid].u_obj, &len); - memcpy(wifi_sta_config.sta.ssid, p, MIN(len, sizeof(wifi_sta_config.sta.ssid))); - #if MICROPY_PY_CHANNEL_ENABLE - strcpy(_amp_ssid, p); - #endif - - } - if (args[ARG_password].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_password].u_obj, &len); - memcpy(wifi_sta_config.sta.password, p, MIN(len, sizeof(wifi_sta_config.sta.password))); - #if MICROPY_PY_CHANNEL_ENABLE - strcpy(_amp_password, p); - #endif - } - if (args[ARG_bssid].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_bssid].u_obj, &len); - if (len != sizeof(wifi_sta_config.sta.bssid)) { - mp_raise_ValueError(NULL); - } - wifi_sta_config.sta.bssid_set = 1; - memcpy(wifi_sta_config.sta.bssid, p, sizeof(wifi_sta_config.sta.bssid)); - } - ESP_EXCEPTIONS(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config)); - } - - wifi_sta_reconnects = 0; - // connect to the WiFi AP - MP_THREAD_GIL_EXIT(); - ESP_EXCEPTIONS(esp_wifi_connect()); - MP_THREAD_GIL_ENTER(); - wifi_sta_connect_requested = true; - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp_connect_obj, 1, esp_connect); - -STATIC mp_obj_t esp_disconnect(mp_obj_t self_in) { - wifi_sta_connect_requested = false; - ESP_EXCEPTIONS(esp_wifi_disconnect()); - return mp_const_none; -} - -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_disconnect_obj, esp_disconnect); - -// Cases similar to ESP8266 user_interface.h -// Error cases are referenced from wifi_err_reason_t in ESP-IDF -enum { - STAT_IDLE = 1000, - STAT_CONNECTING = 1001, - STAT_GOT_IP = 1010, -}; - -STATIC mp_obj_t esp_status(size_t n_args, const mp_obj_t *args) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - if (n_args == 1) { - if (self->if_id == WIFI_IF_STA) { - // Case of no arg is only for the STA interface - if (wifi_sta_connected) { - // Happy path, connected with IP - return MP_OBJ_NEW_SMALL_INT(STAT_GOT_IP); - } else if (wifi_sta_connect_requested - && (conf_wifi_sta_reconnects == 0 - || wifi_sta_reconnects < conf_wifi_sta_reconnects)) { - // No connection or error, but is requested = Still connecting - return MP_OBJ_NEW_SMALL_INT(STAT_CONNECTING); - } else if (wifi_sta_disconn_reason == 0) { - // No activity, No error = Idle - return MP_OBJ_NEW_SMALL_INT(STAT_IDLE); - } else { - // Simply pass the error through from ESP-identifier - return MP_OBJ_NEW_SMALL_INT(wifi_sta_disconn_reason); - } - } - return mp_const_none; - } - - // one argument: return status based on query parameter - switch ((uintptr_t)args[1]) { - case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_stations): { - // return list of connected stations, only if in soft-AP mode - require_if(args[0], WIFI_IF_AP); - wifi_sta_list_t station_list; - ESP_EXCEPTIONS(esp_wifi_ap_get_sta_list(&station_list)); - wifi_sta_info_t *stations = (wifi_sta_info_t *)station_list.sta; - mp_obj_t list = mp_obj_new_list(0, NULL); - for (int i = 0; i < station_list.num; ++i) { - mp_obj_tuple_t *t = mp_obj_new_tuple(1, NULL); - t->items[0] = mp_obj_new_bytes(stations[i].mac, sizeof(stations[i].mac)); - mp_obj_list_append(list, t); - } - return list; - } - case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_rssi): { - // return signal of AP, only in STA mode - require_if(args[0], WIFI_IF_STA); - - wifi_ap_record_t info; - ESP_EXCEPTIONS(esp_wifi_sta_get_ap_info(&info)); - return MP_OBJ_NEW_SMALL_INT(info.rssi); - } - default: - mp_raise_ValueError(MP_ERROR_TEXT("unknown status param")); - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_status_obj, 1, 2, esp_status); - -STATIC mp_obj_t esp_scan(mp_obj_t self_in) { - // check that STA mode is active - wifi_mode_t mode; - ESP_EXCEPTIONS(esp_wifi_get_mode(&mode)); - if ((mode & WIFI_MODE_STA) == 0) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("STA must be active")); - } - - mp_obj_t list = mp_obj_new_list(0, NULL); - wifi_scan_config_t config = { 0 }; - config.show_hidden = true; - MP_THREAD_GIL_EXIT(); - esp_err_t status = esp_wifi_scan_start(&config, 1); - MP_THREAD_GIL_ENTER(); - if (status == 0) { - uint16_t count = 0; - ESP_EXCEPTIONS(esp_wifi_scan_get_ap_num(&count)); - wifi_ap_record_t *wifi_ap_records = calloc(count, sizeof(wifi_ap_record_t)); - ESP_EXCEPTIONS(esp_wifi_scan_get_ap_records(&count, wifi_ap_records)); - for (uint16_t i = 0; i < count; i++) { - mp_obj_tuple_t *t = mp_obj_new_tuple(6, NULL); - uint8_t *x = memchr(wifi_ap_records[i].ssid, 0, sizeof(wifi_ap_records[i].ssid)); - int ssid_len = x ? x - wifi_ap_records[i].ssid : sizeof(wifi_ap_records[i].ssid); - t->items[0] = mp_obj_new_bytes(wifi_ap_records[i].ssid, ssid_len); - t->items[1] = mp_obj_new_bytes(wifi_ap_records[i].bssid, sizeof(wifi_ap_records[i].bssid)); - t->items[2] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].primary); - t->items[3] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].rssi); - t->items[4] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].authmode); - t->items[5] = mp_const_false; // XXX hidden? - mp_obj_list_append(list, MP_OBJ_FROM_PTR(t)); - } - free(wifi_ap_records); - } - return list; -} - -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_scan_obj, esp_scan); - -STATIC mp_obj_t esp_isconnected(mp_obj_t self_in) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (self->if_id == WIFI_IF_STA) { - return mp_obj_new_bool(wifi_sta_connected); - } else { - wifi_sta_list_t sta; - esp_wifi_ap_get_sta_list(&sta); - return mp_obj_new_bool(sta.num != 0); - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_isconnected_obj, esp_isconnected); - STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); tcpip_adapter_ip_info_t info; @@ -565,18 +181,18 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { if (self->if_id == WIFI_IF_STA || self->if_id == ESP_IF_ETH) { esp_err_t e = tcpip_adapter_dhcpc_stop(self->if_id); if (e != ESP_OK && e != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { - _esp_exceptions(e); + esp_exceptions_helper(e); } - ESP_EXCEPTIONS(tcpip_adapter_set_ip_info(self->if_id, &info)); - ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(self->if_id, DNS_MAIN, &dns_info)); + esp_exceptions(tcpip_adapter_set_ip_info(self->if_id, &info)); + esp_exceptions(tcpip_adapter_set_dns_info(self->if_id, DNS_MAIN, &dns_info)); } else if (self->if_id == WIFI_IF_AP) { esp_err_t e = tcpip_adapter_dhcps_stop(WIFI_IF_AP); if (e != ESP_OK && e != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { - _esp_exceptions(e); + esp_exceptions_helper(e); } - ESP_EXCEPTIONS(tcpip_adapter_set_ip_info(WIFI_IF_AP, &info)); - ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(WIFI_IF_AP, DNS_MAIN, &dns_info)); - ESP_EXCEPTIONS(tcpip_adapter_dhcps_start(WIFI_IF_AP)); + esp_exceptions(tcpip_adapter_set_ip_info(WIFI_IF_AP, &info)); + esp_exceptions(tcpip_adapter_set_dns_info(WIFI_IF_AP, DNS_MAIN, &dns_info)); + esp_exceptions(tcpip_adapter_dhcps_start(WIFI_IF_AP)); } } else { // check for the correct string @@ -584,234 +200,32 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { if ((self->if_id != WIFI_IF_STA && self->if_id != ESP_IF_ETH) || strcmp("dhcp", mode)) { mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); } - ESP_EXCEPTIONS(tcpip_adapter_dhcpc_start(self->if_id)); + esp_exceptions(tcpip_adapter_dhcpc_start(self->if_id)); } return mp_const_none; } } - MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj, 1, 2, esp_ifconfig); -STATIC mp_obj_t esp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - if (n_args != 1 && kwargs->used != 0) { - mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); - } - - wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - bool is_wifi = self->if_id == WIFI_IF_AP || self->if_id == WIFI_IF_STA; - - wifi_config_t cfg; - if (is_wifi) { - ESP_EXCEPTIONS(esp_wifi_get_config(self->if_id, &cfg)); - } - - #define QS(x) (uintptr_t)MP_OBJ_NEW_QSTR(x) - - if (kwargs->used != 0) { - if (!is_wifi) { - goto unknown; - } - - for (size_t i = 0; i < kwargs->alloc; i++) { - if (mp_map_slot_is_filled(kwargs, i)) { - int req_if = -1; - - switch ((uintptr_t)kwargs->table[i].key) { - case QS(MP_QSTR_mac): { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); - if (bufinfo.len != 6) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); - } - ESP_EXCEPTIONS(esp_wifi_set_mac(self->if_id, bufinfo.buf)); - break; - } - case QS(MP_QSTR_essid): { - req_if = WIFI_IF_AP; - size_t len; - const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); - len = MIN(len, sizeof(cfg.ap.ssid)); - memcpy(cfg.ap.ssid, s, len); - cfg.ap.ssid_len = len; - break; - } - case QS(MP_QSTR_hidden): { - req_if = WIFI_IF_AP; - cfg.ap.ssid_hidden = mp_obj_is_true(kwargs->table[i].value); - break; - } - case QS(MP_QSTR_authmode): { - req_if = WIFI_IF_AP; - cfg.ap.authmode = mp_obj_get_int(kwargs->table[i].value); - break; - } - case QS(MP_QSTR_password): { - req_if = WIFI_IF_AP; - size_t len; - const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); - len = MIN(len, sizeof(cfg.ap.password) - 1); - memcpy(cfg.ap.password, s, len); - cfg.ap.password[len] = 0; - break; - } - case QS(MP_QSTR_channel): { - req_if = WIFI_IF_AP; - cfg.ap.channel = mp_obj_get_int(kwargs->table[i].value); - break; - } - case QS(MP_QSTR_dhcp_hostname): { - const char *s = mp_obj_str_get_str(kwargs->table[i].value); - ESP_EXCEPTIONS(tcpip_adapter_set_hostname(self->if_id, s)); - break; - } - case QS(MP_QSTR_max_clients): { - req_if = WIFI_IF_AP; - cfg.ap.max_connection = mp_obj_get_int(kwargs->table[i].value); - break; - } - case QS(MP_QSTR_reconnects): { - int reconnects = mp_obj_get_int(kwargs->table[i].value); - req_if = WIFI_IF_STA; - // parameter reconnects == -1 means to retry forever. - // here means conf_wifi_sta_reconnects == 0 to retry forever. - conf_wifi_sta_reconnects = (reconnects == -1) ? 0 : reconnects + 1; - break; - } - default: - goto unknown; - } - - // We post-check interface requirements to save on code size - if (req_if >= 0) { - require_if(args[0], req_if); - } - } - } - - ESP_EXCEPTIONS(esp_wifi_set_config(self->if_id, &cfg)); - - return mp_const_none; - } - - // Get config - - if (n_args != 2) { - mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); - } - - int req_if = -1; - mp_obj_t val = mp_const_none; - - switch ((uintptr_t)args[1]) { - case QS(MP_QSTR_mac): { - uint8_t mac[6]; - switch (self->if_id) { - case WIFI_IF_AP: // fallthrough intentional - case WIFI_IF_STA: - ESP_EXCEPTIONS(esp_wifi_get_mac(self->if_id, mac)); - return mp_obj_new_bytes(mac, sizeof(mac)); - default: - goto unknown; - } - } - case QS(MP_QSTR_essid): - switch (self->if_id) { - case WIFI_IF_STA: - val = mp_obj_new_str((char *)cfg.sta.ssid, strlen((char *)cfg.sta.ssid)); - break; - case WIFI_IF_AP: - val = mp_obj_new_str((char *)cfg.ap.ssid, cfg.ap.ssid_len); - break; - default: - req_if = WIFI_IF_AP; - } - break; - case QS(MP_QSTR_password): - switch (self->if_id) { - case WIFI_IF_STA: - val = mp_obj_new_str((char *)cfg.sta.password, strlen((char *)cfg.sta.password)); - break; - case WIFI_IF_AP: - val = mp_obj_new_str((char *)cfg.ap.password, strlen((char *)cfg.sta.password)); - break; - default: - req_if = WIFI_IF_AP; - } - break; - case QS(MP_QSTR_hidden): - req_if = WIFI_IF_AP; - val = mp_obj_new_bool(cfg.ap.ssid_hidden); - break; - case QS(MP_QSTR_authmode): - req_if = WIFI_IF_AP; - val = MP_OBJ_NEW_SMALL_INT(cfg.ap.authmode); - break; - case QS(MP_QSTR_channel): - req_if = WIFI_IF_AP; - val = MP_OBJ_NEW_SMALL_INT(cfg.ap.channel); - break; - case QS(MP_QSTR_dhcp_hostname): { - const char *s; - ESP_EXCEPTIONS(tcpip_adapter_get_hostname(self->if_id, &s)); - val = mp_obj_new_str(s, strlen(s)); - break; - } - case QS(MP_QSTR_max_clients): { - val = MP_OBJ_NEW_SMALL_INT(cfg.ap.max_connection); - break; - } - case QS(MP_QSTR_reconnects): - req_if = WIFI_IF_STA; - int rec = conf_wifi_sta_reconnects - 1; - val = MP_OBJ_NEW_SMALL_INT(rec); - break; - default: - goto unknown; - } - -#undef QS - - // We post-check interface requirements to save on code size - if (req_if >= 0) { - require_if(args[0], req_if); - } - - return val; - -unknown: - mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); -} -MP_DEFINE_CONST_FUN_OBJ_KW(esp_config_obj, 1, esp_config); - -STATIC const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&esp_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&esp_connect_obj) }, - { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&esp_disconnect_obj) }, - { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&esp_status_obj) }, - { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&esp_scan_obj) }, - { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&esp_isconnected_obj) }, - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&esp_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_ifconfig_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); - -const mp_obj_type_t wlan_if_type = { - { &mp_type_type }, - .name = MP_QSTR_WLAN, - .locals_dict = (mp_obj_t)&wlan_if_locals_dict, -}; - STATIC mp_obj_t esp_phy_mode(size_t n_args, const mp_obj_t *args) { return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_phy_mode_obj, 0, 1, esp_phy_mode); +#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(4, 3, 0) +#define TEST_WIFI_AUTH_MAX 9 +#else +#define TEST_WIFI_AUTH_MAX 8 +#endif +_Static_assert(WIFI_AUTH_MAX == TEST_WIFI_AUTH_MAX, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); + STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&esp_initialize_obj) }, + + #if MICROPY_PY_NETWORK_WLAN { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&get_wlan_obj) }, + #endif #if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 1) && (CONFIG_IDF_TARGET_ESP32) { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&get_lan_obj) }, @@ -820,6 +234,8 @@ STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_phy_mode_obj) }, #if MODNETWORK_INCLUDE_CONSTANTS + + #if MICROPY_PY_NETWORK_WLAN { MP_ROM_QSTR(MP_QSTR_STA_IF), MP_ROM_INT(WIFI_IF_STA)}, { MP_ROM_QSTR(MP_QSTR_AP_IF), MP_ROM_INT(WIFI_IF_AP)}, @@ -833,11 +249,13 @@ STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_WPA_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA_WPA2_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_ENTERPRISE), MP_ROM_INT(WIFI_AUTH_WPA2_ENTERPRISE) }, - #if 0 // TODO: Remove this #if/#endif when lastest ISP IDF will be used { MP_ROM_QSTR(MP_QSTR_AUTH_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, + #if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(4, 3, 0) + { MP_ROM_QSTR(MP_QSTR_AUTH_WAPI_PSK), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, #endif { MP_ROM_QSTR(MP_QSTR_AUTH_MAX), MP_ROM_INT(WIFI_AUTH_MAX) }, + #endif #if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 1) && (CONFIG_IDF_TARGET_ESP32) { MP_ROM_QSTR(MP_QSTR_PHY_LAN8720), MP_ROM_INT(PHY_LAN8720) }, diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index be6aa67068..7bcfa0e6fc 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -26,14 +26,39 @@ #ifndef MICROPY_INCLUDED_ESP32_MODNETWORK_H #define MICROPY_INCLUDED_ESP32_MODNETWORK_H +#include "esp_event.h" + enum { PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041 }; enum { ETH_INITIALIZED, ETH_STARTED, ETH_STOPPED, ETH_CONNECTED, ETH_DISCONNECTED, ETH_GOT_IP }; +// Cases similar to ESP8266 user_interface.h +// Error cases are referenced from wifi_err_reason_t in ESP-IDF +enum { + STAT_IDLE = 1000, + STAT_CONNECTING = 1001, + STAT_GOT_IP = 1010, +}; + +typedef struct _wlan_if_obj_t { + mp_obj_base_t base; + int if_id; +} wlan_if_obj_t; + +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(get_wlan_obj); MP_DECLARE_CONST_FUN_OBJ_KW(get_lan_obj); MP_DECLARE_CONST_FUN_OBJ_1(ppp_make_new_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_config_obj); +NORETURN void esp_exceptions_helper(esp_err_t e); + +static inline void esp_exceptions(esp_err_t e) { + if (e != ESP_OK) { + esp_exceptions_helper(e); + } +} + void usocket_events_deinit(void); +void network_wlan_event_handler(system_event_t *event); #endif diff --git a/ports/esp32/modules/uai.py b/ports/esp32/modules/uai.py deleted file mode 100644 index c8ec15e3ce..0000000000 --- a/ports/esp32/modules/uai.py +++ /dev/null @@ -1,63 +0,0 @@ -from minicv import ML - -AI_ENGINE_ALIYUN = 1 -AI_ENGINE_NATIVE = 2 - -class AI: - def __init__(self, type=AI_ENGINE_NATIVE, accessKey=None, accessSecret=None, ossEndpoint=None, ossBucket=None): - self.type = type - self.ml = ML() - if (self.type == AI_ENGINE_ALIYUN): - self.ml.open(self.ml.ML_ENGINE_CLOUD) - if not accessKey or not accessSecret: - print('access key can not be null') - return - else: - self.ml.config(accessKey, accessSecret, ossEndpoint, ossBucket) - else: - print('now only support cloud ai, not support nativate ai yet') - print("Please use example: ai = AI(AI.AI_ENGINE_CLOUD, 'Your-Access-Key', 'Your-Access-Secret')") - - # 人脸比对 - def compareFace(self, imagePath, compareFacePath): - self.ml.setInputData(imagePath, compareFacePath) - self.ml.loadNet("FacebodyComparing") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - # 人体检测 - def detectPedestrian(self, imagePath): - self.ml.setInputData(imagePath) - self.ml.loadNet("DetectPedestrian") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - # 水果检测 - def detectFruits(self, imagePath): - self.ml.setInputData(imagePath, None) - self.ml.loadNet("DetectFruits") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - # 车牌识别 - def recognizeLicensePlate(self, imagePath): - self.ml.setInputData(imagePath) - self.ml.loadNet("RecognizeLicensePlate") - self.ml.predict() - resp = self.ml.getPredictResponses(None) - self.ml.unLoadNet() - return resp - - def __del__(self): - try: - self.ml.close() - del self.type - del self.ml - except Exception: - pass diff --git a/ports/esp32/moduos.c b/ports/esp32/moduos.c index d0c28c0063..50c0d7ea5c 100644 --- a/ports/esp32/moduos.c +++ b/ports/esp32/moduos.c @@ -1,10 +1,12 @@ /* * This file is part of the MicroPython project, http://micropython.org/ * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * * The MIT License (MIT) * - * Copyright (c) 2014-2018 Paul Sokolovsky - * Copyright (c) 2014-2018 Damien P. George + * Copyright (c) 2015 Josef Gajdusek + * Copyright (c) 2016 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,103 +27,66 @@ * THE SOFTWARE. */ -#include -#include -#include -#include -#include #include -#include -#include -#include -#include "dirent.h" -#if MICROPY_VFS_FAT -#include "lib/oofatfs/diskio.h" -#include "lib/oofatfs/ff.h" -#endif -#include "esp_log.h" +#include "esp_system.h" + +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" #include "extmod/misc.h" #include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "extmod/vfs_lfs.h" #include "genhdr/mpversion.h" -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "py/mpthread.h" -#include "py/objstr.h" -#include "py/objtuple.h" -#include "py/runtime.h" - -#if MICROPY_VFS_POSIX -#include "extmod/vfs_posix.h" -#endif - -#define TAG "MOD_OS" extern const mp_obj_type_t mp_fat_vfs_type; -STATIC const qstr os_uname_info_fields[] = { MP_QSTR_sysname, MP_QSTR_nodename, MP_QSTR_release, MP_QSTR_version, - MP_QSTR_machine }; +STATIC const qstr os_uname_info_fields[] = { + MP_QSTR_sysname, MP_QSTR_nodename, + MP_QSTR_release, MP_QSTR_version, MP_QSTR_machine +}; STATIC const MP_DEFINE_STR_OBJ(os_uname_info_sysname_obj, MICROPY_PY_SYS_PLATFORM); -STATIC const MP_DEFINE_STR_OBJ(os_uname_info_nodename_obj, MICROPY_PY_SYS_NODE); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_nodename_obj, MICROPY_PY_SYS_PLATFORM); STATIC const MP_DEFINE_STR_OBJ(os_uname_info_release_obj, MICROPY_VERSION_STRING); STATIC const MP_DEFINE_STR_OBJ(os_uname_info_version_obj, MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE); STATIC const MP_DEFINE_STR_OBJ(os_uname_info_machine_obj, MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME); -STATIC MP_DEFINE_ATTRTUPLE(os_uname_info_obj, os_uname_info_fields, 5, (mp_obj_t)&os_uname_info_sysname_obj, - (mp_obj_t)&os_uname_info_nodename_obj, (mp_obj_t)&os_uname_info_release_obj, - (mp_obj_t)&os_uname_info_version_obj, (mp_obj_t)&os_uname_info_machine_obj); - -STATIC mp_obj_t os_uname(void) -{ +STATIC MP_DEFINE_ATTRTUPLE( + os_uname_info_obj, + os_uname_info_fields, + 5, + (mp_obj_t)&os_uname_info_sysname_obj, + (mp_obj_t)&os_uname_info_nodename_obj, + (mp_obj_t)&os_uname_info_release_obj, + (mp_obj_t)&os_uname_info_version_obj, + (mp_obj_t)&os_uname_info_machine_obj + ); + +STATIC mp_obj_t os_uname(void) { return (mp_obj_t)&os_uname_info_obj; } STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_uname_obj, os_uname); -STATIC mp_obj_t os_version(void) -{ - char version[40] = { 0 }; - sprintf(version, "%s-v%s", MICROPY_SW_VENDOR_NAME, SYSINFO_SYSTEM_VERSION); - return mp_obj_new_str(version, strlen(version)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_version_obj, os_version); - -STATIC mp_obj_t os_version_info(void) -{ - char version_info[80] = { 0 }; -#ifdef MICROPY_HW_BOARD_TYPE - sprintf(version_info, "%s-%s-%s-v%s-%s", MICROPY_SW_VENDOR_NAME, MICROPY_HW_MCU_NAME, MICROPY_HW_BOARD_TYPE, - SYSINFO_SYSTEM_VERSION, MICROPY_BUILD_DATE); -#else - sprintf(version_info, "%s-%s-v%s-%s", MICROPY_SW_VENDOR_NAME, MICROPY_HW_MCU_NAME, SYSINFO_SYSTEM_VERSION, - MICROPY_BUILD_DATE); -#endif - return mp_obj_new_str(version_info, strlen(version_info)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_version_info_obj, os_version_info); - -STATIC mp_obj_t os_urandom(mp_obj_t num) -{ +STATIC mp_obj_t os_urandom(mp_obj_t num) { mp_int_t n = mp_obj_get_int(num); vstr_t vstr; vstr_init_len(&vstr, n); - mp_uint_t r = 0; + uint32_t r = 0; for (int i = 0; i < n; i++) { if ((i & 3) == 0) { - r = rand(); + r = esp_random(); // returns 32-bit hardware random number } vstr.buf[i] = r; r >>= 8; } - vstr.buf[n] = '\0'; return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_urandom_obj, os_urandom); - #if MICROPY_PY_OS_DUPTERM -STATIC mp_obj_t os_dupterm_notify(mp_obj_t obj_in) -{ +STATIC mp_obj_t os_dupterm_notify(mp_obj_t obj_in) { (void)obj_in; for (;;) { int c = mp_uos_dupterm_rx_chr(); @@ -129,67 +94,23 @@ STATIC mp_obj_t os_dupterm_notify(mp_obj_t obj_in) break; } ringbuf_put(&stdin_ringbuf, c); - mp_hal_wake_main_task_from_isr(); } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_dupterm_notify_obj, os_dupterm_notify); #endif -extern void aos_task_show_info(); -STATIC mp_obj_t mod_os_tasklist(void) -{ - aos_task_show_info(); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_os_tasklist_obj, mod_os_tasklist); - -extern void aos_mm_show_info(void); -STATIC mp_obj_t mod_os_dumpsys_mm(void) -{ - aos_mm_show_info(); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_os_dumpsys_mm_obj, mod_os_dumpsys_mm); - - -STATIC mp_obj_t mod_os_plussys_mm(void) -{ -#if CONFIG_BT_ENABLED - #include "esp_bt.h" - esp_bt_mem_release(ESP_BT_MODE_BTDM); -#endif - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_os_plussys_mm_obj, mod_os_plussys_mm); - -#include "esp_heap_caps.h" -STATIC mp_obj_t os_heap(void) -{ - printf("[free-defaut] %d\r\n", esp_get_free_heap_size()); - printf("[free-8bit-internal] %d\r\n", heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL)); -#if CONFIG_SPIRAM_USE_MALLOC - printf("[free-8bit-spiram] %d\r\n", heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM)); -#endif - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_heap_obj, os_heap); - STATIC const mp_rom_map_elem_t os_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, { MP_ROM_QSTR(MP_QSTR_uname), MP_ROM_PTR(&os_uname_obj) }, { MP_ROM_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&os_urandom_obj) }, - { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&os_version_obj) }, - { MP_ROM_QSTR(MP_QSTR_version_info), MP_ROM_PTR(&os_version_info_obj) }, - -#if MICROPY_PY_OS_DUPTERM + #if MICROPY_PY_OS_DUPTERM { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, { MP_ROM_QSTR(MP_QSTR_dupterm_notify), MP_ROM_PTR(&os_dupterm_notify_obj) }, -#endif - -#if MICROPY_VFS - { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, + #endif + #if MICROPY_VFS { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mp_vfs_mkdir_obj) }, { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mp_vfs_rmdir_obj) }, { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&mp_vfs_chdir_obj) }, @@ -200,23 +121,16 @@ STATIC const mp_rom_map_elem_t os_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) }, { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&mp_vfs_mount_obj) }, { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_vfs_umount_obj) }, -#if MICROPY_VFS_POSIX - { MP_ROM_QSTR(MP_QSTR_VfsPosix), MP_ROM_PTR(&mp_type_vfs_posix) }, -#endif -#if MICROPY_VFS_FAT + #if MICROPY_VFS_FAT { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, -#endif -#if MICROPY_VFS_LFS1 + #endif + #if MICROPY_VFS_LFS1 { MP_ROM_QSTR(MP_QSTR_VfsLfs1), MP_ROM_PTR(&mp_type_vfs_lfs1) }, -#endif -#if MICROPY_VFS_LFS2 + #endif + #if MICROPY_VFS_LFS2 { MP_ROM_QSTR(MP_QSTR_VfsLfs2), MP_ROM_PTR(&mp_type_vfs_lfs2) }, -#endif -#endif - { MP_ROM_QSTR(MP_QSTR_heap), MP_ROM_PTR(&os_heap_obj) }, - { MP_ROM_QSTR(MP_QSTR_tasklist), MP_ROM_PTR(&mod_os_tasklist_obj) }, - { MP_ROM_QSTR(MP_QSTR_dumpsys_mm), MP_ROM_PTR(&mod_os_dumpsys_mm_obj) }, - { MP_ROM_QSTR(MP_QSTR_plussys_mm), MP_ROM_PTR(&mod_os_plussys_mm_obj) }, + #endif + #endif }; STATIC MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index a713b89ee3..dc6bc2e53b 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -8,7 +8,7 @@ #include #include "esp_system.h" #include "freertos/FreeRTOS.h" -// #include "driver/i2s.h" +#include "driver/i2s.h" // object representation and NLR handling #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) @@ -32,6 +32,8 @@ // optimisations #define MICROPY_OPT_COMPUTED_GOTO (1) +#define MICROPY_OPT_LOAD_ATTR_FAST_PATH (1) +#define MICROPY_OPT_MAP_LOOKUP_CACHE (1) #define MICROPY_OPT_MPZ_BITWISE (1) // Python internal features @@ -61,11 +63,8 @@ #define MICROPY_USE_INTERNAL_ERRNO (0) // errno.h from xtensa-esp32-elf/sys-include/sys #define MICROPY_USE_INTERNAL_PRINTF (0) // ESP32 SDK requires its own printf #define MICROPY_ENABLE_SCHEDULER (1) -#define MICROPY_SCHEDULER_DEPTH (8 * 2) +#define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_VFS (1) -#define MICROPY_VFS_POSIX (1) -#define MICROPY_VFS_FAT (1) -#define MICROPY_FATFS_NORTC (1) // control over Python builtins #define MICROPY_PY_FUNCTION_ATTRS (1) @@ -141,7 +140,6 @@ #define MICROPY_BLUETOOTH_NIMBLE (1) #define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (1) #endif -#define MICROPY_EPOCH_IS_1970 (1) #define MICROPY_PY_UASYNCIO (1) #define MICROPY_PY_UCTYPES (1) #define MICROPY_PY_UZLIB (1) @@ -164,16 +162,26 @@ #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_BITSTREAM (1) #define MICROPY_PY_MACHINE_PULSE (1) +#define MICROPY_PY_MACHINE_PWM (1) +#define MICROPY_PY_MACHINE_PWM_INIT (1) +#define MICROPY_PY_MACHINE_PWM_DUTY (1) +#define MICROPY_PY_MACHINE_PWM_DUTY_U16_NS (1) +#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/esp32/machine_pwm.c" #define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_SOFTI2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (0) #define MICROPY_PY_MACHINE_SPI_LSB (1) +#define MICROPY_PY_MACHINE_SOFTSPI (1) #ifndef MICROPY_PY_MACHINE_DAC #define MICROPY_PY_MACHINE_DAC (1) #endif #ifndef MICROPY_PY_MACHINE_I2S #define MICROPY_PY_MACHINE_I2S (1) #endif +#ifndef MICROPY_PY_NETWORK_WLAN +#define MICROPY_PY_NETWORK_WLAN (1) +#endif #ifndef MICROPY_HW_ENABLE_SDCARD #define MICROPY_HW_ENABLE_SDCARD (1) #endif @@ -186,6 +194,8 @@ #define MICROPY_PY_WEBREPL (1) #define MICROPY_PY_FRAMEBUF (1) #define MICROPY_PY_BTREE (1) +#define MICROPY_PY_ONEWIRE (1) +#define MICROPY_PY_UPLATFORM (1) #define MICROPY_PY_USOCKET_EVENTS (MICROPY_PY_WEBREPL) #define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1) #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME ("ESP32") @@ -195,8 +205,8 @@ #define MICROPY_FATFS_RPATH (2) #define MICROPY_FATFS_MAX_SS (4096) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ -#define mp_type_fileio mp_type_vfs_posix_fileio -#define mp_type_textio mp_type_vfs_posix_textio +#define mp_type_fileio mp_type_vfs_fat_fileio +#define mp_type_textio mp_type_vfs_fat_textio // use vfs's functions for import stat and builtin open #define mp_import_stat mp_vfs_import_stat @@ -232,34 +242,18 @@ extern const struct _mp_obj_module_t mp_module_onewire; struct _machine_timer_obj_t; -#if MICROPY_PY_LVGL -#ifndef MICROPY_INCLUDED_PY_MPSTATE_H -#define MICROPY_INCLUDED_PY_MPSTATE_H -#include "misc/lv_gc.h" -#undef MICROPY_INCLUDED_PY_MPSTATE_H -#else -#include "misc/lv_gc.h" -#endif -#else -#define LV_ROOTS -#endif - #if MICROPY_BLUETOOTH_NIMBLE struct mp_bluetooth_nimble_root_pointers_t; -#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE \ - struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; +#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; #else #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE #endif -#include "soc/soc_caps.h" #define MICROPY_PORT_ROOT_POINTERS \ - LV_ROOTS \ - void *mp_lv_user_data; \ const char *readline_hist[8]; \ mp_obj_t machine_pin_irq_handler[40]; \ struct _machine_timer_obj_t *machine_timer_obj_head; \ - struct _machine_i2s_obj_t *machine_i2s_obj[SOC_I2S_NUM]; \ + struct _machine_i2s_obj_t *machine_i2s_obj[I2S_NUM_MAX]; \ MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE // type definitions for the specific machine @@ -277,9 +271,7 @@ void *esp_native_code_commit(void *, size_t, void *); #define MICROPY_END_ATOMIC_SECTION(state) portEXIT_CRITICAL_NESTED(state) #if MICROPY_PY_USOCKET_EVENTS -#define MICROPY_PY_USOCKET_EVENTS_HANDLER \ - extern void usocket_events_handler(void); \ - usocket_events_handler(); +#define MICROPY_PY_USOCKET_EVENTS_HANDLER extern void usocket_events_handler(void); usocket_events_handler(); #else #define MICROPY_PY_USOCKET_EVENTS_HANDLER #endif @@ -304,6 +296,12 @@ void *esp_native_code_commit(void *, size_t, void *); #endif // Functions that should go in IRAM +#define MICROPY_WRAP_MP_BINARY_OP(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_EXECUTE_BYTECODE(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_LOAD_GLOBAL(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_LOAD_NAME(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_MAP_LOOKUP(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_OBJ_GET_TYPE(f) IRAM_ATTR f #define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) IRAM_ATTR f #define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) IRAM_ATTR f @@ -318,7 +316,11 @@ typedef long mp_off_t; // board specifics #define MICROPY_PY_SYS_PLATFORM "esp32" -#define MICROPY_PY_SYS_NODE "V3.3" + +// ESP32-S3 extended IO for 47 & 48 +#ifndef MICROPY_HW_ESP32S3_EXTENDED_IO +#define MICROPY_HW_ESP32S3_EXTENDED_IO (1) +#endif #ifndef MICROPY_HW_ENABLE_MDNS_QUERIES #define MICROPY_HW_ENABLE_MDNS_QUERIES (1) @@ -327,3 +329,9 @@ typedef long mp_off_t; #ifndef MICROPY_HW_ENABLE_MDNS_RESPONDER #define MICROPY_HW_ENABLE_MDNS_RESPONDER (1) #endif + +#ifndef MICROPY_BOARD_STARTUP +#define MICROPY_BOARD_STARTUP boardctrl_startup +#endif + +void boardctrl_startup(void); diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c index 2fe06f5a88..de1b173ac4 100644 --- a/ports/esp32/mphalport.c +++ b/ports/esp32/mphalport.c @@ -129,9 +129,7 @@ void mp_hal_stdout_tx_strn(const char *str, size_t len) { if (release_gil) { MP_THREAD_GIL_ENTER(); } -#if MICROPY_PY_OS_DUPTERM mp_uos_dupterm_tx_strn(str, len); -#endif } uint32_t mp_hal_ticks_ms(void) { diff --git a/ports/esp32/mphalport.h b/ports/esp32/mphalport.h index 75403df5b2..01c14ad700 100644 --- a/ports/esp32/mphalport.h +++ b/ports/esp32/mphalport.h @@ -35,6 +35,8 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#define MICROPY_PLATFORM_VERSION "IDF" IDF_VER + // The core that the MicroPython task(s) are pinned to. // Until we move to IDF 4.2+, we need NimBLE on core 0, and for synchronisation // with the ringbuffer and scheduler MP needs to be on the same core. @@ -58,25 +60,6 @@ __attribute__((always_inline)) static inline uint32_t mp_hal_ticks_cpu(void) { #endif return ccount; } -// This macro is used to implement PEP 475 to retry specified syscalls on EINTR -#define MP_HAL_RETRY_SYSCALL(ret, syscall, raise) \ - { \ - for (;;) { \ - MP_THREAD_GIL_EXIT(); \ - ret = syscall; \ - MP_THREAD_GIL_ENTER(); \ - if (ret == -1) { \ - int err = errno; \ - if (err == MP_EINTR) { \ - mp_handle_pending(true); \ - continue; \ - } \ - raise; \ - } \ - break; \ - } \ - } - void mp_hal_delay_us(uint32_t); #define mp_hal_delay_us_fast(us) ets_delay_us(us) diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index 44148a1543..9d1c4a758c 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -59,14 +59,19 @@ STATIC thread_t *thread = NULL; // root pointer, handled by mp_thread_gc_others void mp_thread_init(void *stack, uint32_t stack_len) { mp_thread_set_state(&mp_state_ctx.thread); // create the first entry in the linked list of all threads - thread = &thread_entry0; - thread->id = xTaskGetCurrentTaskHandle(); - thread->ready = 1; - thread->arg = NULL; - thread->stack = stack; - thread->stack_len = stack_len; - thread->next = NULL; + thread_entry0.id = xTaskGetCurrentTaskHandle(); + thread_entry0.ready = 1; + thread_entry0.arg = NULL; + thread_entry0.stack = stack; + thread_entry0.stack_len = stack_len; + thread_entry0.next = NULL; mp_thread_mutex_init(&thread_mutex); + + // memory barrier to ensure above data is committed + __sync_synchronize(); + + // vPortCleanUpTCB needs the thread ready after thread_mutex is ready + thread = &thread_entry0; } void mp_thread_gc_others(void) { @@ -86,11 +91,7 @@ void mp_thread_gc_others(void) { } mp_state_thread_t *mp_thread_get_state(void) { - mp_state_thread_t *state = (mp_state_thread_t *)pvTaskGetThreadLocalStoragePointer(NULL, 1); - if (state == NULL) { - state = (mp_state_thread_t *)pvTaskGetThreadLocalStoragePointer(mp_main_task_handle, 1); - } - return state; + return pvTaskGetThreadLocalStoragePointer(NULL, 1); } void mp_thread_set_state(mp_state_thread_t *state) { diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index 34e5cba258..f302d70fec 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -249,14 +249,13 @@ STATIC mp_obj_t lan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); } lan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - #define QS(x) (uintptr_t)MP_OBJ_NEW_QSTR(x) if (kwargs->used != 0) { for (size_t i = 0; i < kwargs->alloc; i++) { if (mp_map_slot_is_filled(kwargs, i)) { - switch ((uintptr_t)kwargs->table[i].key) { - case QS(MP_QSTR_mac): { + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_mac: { mp_buffer_info_t bufinfo; mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); if (bufinfo.len != 6) { @@ -279,8 +278,8 @@ STATIC mp_obj_t lan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs mp_obj_t val = mp_const_none; - switch ((uintptr_t)args[1]) { - case QS(MP_QSTR_mac): { + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_mac: { uint8_t mac[6]; esp_eth_ioctl(self->eth_handle, ETH_CMD_G_MAC_ADDR, mac); return mp_obj_new_bytes(mac, sizeof(mac)); diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c new file mode 100644 index 0000000000..8702ca7704 --- /dev/null +++ b/ports/esp32/network_wlan.c @@ -0,0 +1,574 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * and Mnemote Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017 Nick Moore @mnemote + * Copyright (c) 2017 "Eric Poulsen" + * + * Based on esp8266/modnetwork.c which is Copyright (c) 2015 Paul Sokolovsky + * And the ESP IDF example code which is Public Domain / CC0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/objlist.h" +#include "py/runtime.h" +#include "modnetwork.h" + +#include "esp_wifi.h" +#include "esp_log.h" +#include "mdns.h" + +#if MICROPY_PY_NETWORK_WLAN + +#if (WIFI_MODE_STA & WIFI_MODE_AP != WIFI_MODE_NULL || WIFI_MODE_STA | WIFI_MODE_AP != WIFI_MODE_APSTA) +#error WIFI_MODE_STA and WIFI_MODE_AP are supposed to be bitfields! +#endif + +STATIC const mp_obj_type_t wlan_if_type; +STATIC const wlan_if_obj_t wlan_sta_obj = {{&wlan_if_type}, WIFI_IF_STA}; +STATIC const wlan_if_obj_t wlan_ap_obj = {{&wlan_if_type}, WIFI_IF_AP}; + +// Set to "true" if esp_wifi_start() was called +static bool wifi_started = false; + +// Set to "true" if the STA interface is requested to be connected by the +// user, used for automatic reassociation. +static bool wifi_sta_connect_requested = false; + +// Set to "true" if the STA interface is connected to wifi and has IP address. +static bool wifi_sta_connected = false; + +// Store the current status. 0 means None here, safe to do so as first enum value is WIFI_REASON_UNSPECIFIED=1. +static uint8_t wifi_sta_disconn_reason = 0; + +#if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER +// Whether mDNS has been initialised or not +static bool mdns_initialised = false; +#endif + +static uint8_t conf_wifi_sta_reconnects = 0; +static uint8_t wifi_sta_reconnects; + +// This function is called by the system-event task and so runs in a different +// thread to the main MicroPython task. It must not raise any Python exceptions. +void network_wlan_event_handler(system_event_t *event) { + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI("wifi", "STA_START"); + wifi_sta_reconnects = 0; + break; + case SYSTEM_EVENT_STA_CONNECTED: + ESP_LOGI("network", "CONNECTED"); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI("network", "GOT_IP"); + wifi_sta_connected = true; + wifi_sta_disconn_reason = 0; // Success so clear error. (in case of new error will be replaced anyway) + #if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER + if (!mdns_initialised) { + mdns_init(); + #if MICROPY_HW_ENABLE_MDNS_RESPONDER + const char *hostname = NULL; + if (tcpip_adapter_get_hostname(WIFI_IF_STA, &hostname) != ESP_OK || hostname == NULL) { + hostname = "esp32"; + } + mdns_hostname_set(hostname); + mdns_instance_name_set(hostname); + #endif + mdns_initialised = true; + } + #endif + break; + case SYSTEM_EVENT_STA_DISCONNECTED: { + // This is a workaround as ESP32 WiFi libs don't currently + // auto-reassociate. + system_event_sta_disconnected_t *disconn = &event->event_info.disconnected; + char *message = ""; + wifi_sta_disconn_reason = disconn->reason; + switch (disconn->reason) { + case WIFI_REASON_BEACON_TIMEOUT: + // AP has dropped out; try to reconnect. + message = "\nbeacon timeout"; + break; + case WIFI_REASON_NO_AP_FOUND: + // AP may not exist, or it may have momentarily dropped out; try to reconnect. + message = "\nno AP found"; + break; + case WIFI_REASON_AUTH_FAIL: + // Password may be wrong, or it just failed to connect; try to reconnect. + message = "\nauthentication failed"; + break; + default: + // Let other errors through and try to reconnect. + break; + } + ESP_LOGI("wifi", "STA_DISCONNECTED, reason:%d%s", disconn->reason, message); + + wifi_sta_connected = false; + if (wifi_sta_connect_requested) { + wifi_mode_t mode; + if (esp_wifi_get_mode(&mode) != ESP_OK) { + break; + } + if (!(mode & WIFI_MODE_STA)) { + break; + } + if (conf_wifi_sta_reconnects) { + ESP_LOGI("wifi", "reconnect counter=%d, max=%d", + wifi_sta_reconnects, conf_wifi_sta_reconnects); + if (++wifi_sta_reconnects >= conf_wifi_sta_reconnects) { + break; + } + } + esp_err_t e = esp_wifi_connect(); + if (e != ESP_OK) { + ESP_LOGI("wifi", "error attempting to reconnect: 0x%04x", e); + } + } + break; + } + default: + break; + } +} + +STATIC void require_if(mp_obj_t wlan_if, int if_no) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(wlan_if); + if (self->if_id != if_no) { + mp_raise_msg(&mp_type_OSError, if_no == WIFI_IF_STA ? MP_ERROR_TEXT("STA required") : MP_ERROR_TEXT("AP required")); + } +} + +STATIC mp_obj_t get_wlan(size_t n_args, const mp_obj_t *args) { + static int initialized = 0; + if (!initialized) { + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_LOGD("modnetwork", "Initializing WiFi"); + esp_exceptions(esp_wifi_init(&cfg)); + esp_exceptions(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_LOGD("modnetwork", "Initialized"); + initialized = 1; + } + + int idx = (n_args > 0) ? mp_obj_get_int(args[0]) : WIFI_IF_STA; + if (idx == WIFI_IF_STA) { + return MP_OBJ_FROM_PTR(&wlan_sta_obj); + } else if (idx == WIFI_IF_AP) { + return MP_OBJ_FROM_PTR(&wlan_ap_obj); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid WLAN interface identifier")); + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(get_wlan_obj, 0, 1, get_wlan); + +STATIC mp_obj_t network_wlan_active(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + wifi_mode_t mode; + if (!wifi_started) { + mode = WIFI_MODE_NULL; + } else { + esp_exceptions(esp_wifi_get_mode(&mode)); + } + + int bit = (self->if_id == WIFI_IF_STA) ? WIFI_MODE_STA : WIFI_MODE_AP; + + if (n_args > 1) { + bool active = mp_obj_is_true(args[1]); + mode = active ? (mode | bit) : (mode & ~bit); + if (mode == WIFI_MODE_NULL) { + if (wifi_started) { + esp_exceptions(esp_wifi_stop()); + wifi_started = false; + } + } else { + esp_exceptions(esp_wifi_set_mode(mode)); + if (!wifi_started) { + esp_exceptions(esp_wifi_start()); + wifi_started = true; + } + } + } + + return (mode & bit) ? mp_const_true : mp_const_false; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_active_obj, 1, 2, network_wlan_active); + +STATIC mp_obj_t network_wlan_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_ssid, ARG_password, ARG_bssid }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + wifi_config_t wifi_sta_config = {0}; + + // configure any parameters that are given + if (n_args > 1) { + size_t len; + const char *p; + if (args[ARG_ssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_ssid].u_obj, &len); + memcpy(wifi_sta_config.sta.ssid, p, MIN(len, sizeof(wifi_sta_config.sta.ssid))); + } + if (args[ARG_password].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_password].u_obj, &len); + memcpy(wifi_sta_config.sta.password, p, MIN(len, sizeof(wifi_sta_config.sta.password))); + } + if (args[ARG_bssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_bssid].u_obj, &len); + if (len != sizeof(wifi_sta_config.sta.bssid)) { + mp_raise_ValueError(NULL); + } + wifi_sta_config.sta.bssid_set = 1; + memcpy(wifi_sta_config.sta.bssid, p, sizeof(wifi_sta_config.sta.bssid)); + } + esp_exceptions(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config)); + } + + wifi_sta_reconnects = 0; + // connect to the WiFi AP + MP_THREAD_GIL_EXIT(); + esp_exceptions(esp_wifi_connect()); + MP_THREAD_GIL_ENTER(); + wifi_sta_connect_requested = true; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_connect_obj, 1, network_wlan_connect); + +STATIC mp_obj_t network_wlan_disconnect(mp_obj_t self_in) { + wifi_sta_connect_requested = false; + esp_exceptions(esp_wifi_disconnect()); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_disconnect_obj, network_wlan_disconnect); + +STATIC mp_obj_t network_wlan_status(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args == 1) { + if (self->if_id == WIFI_IF_STA) { + // Case of no arg is only for the STA interface + if (wifi_sta_connected) { + // Happy path, connected with IP + return MP_OBJ_NEW_SMALL_INT(STAT_GOT_IP); + } else if (wifi_sta_connect_requested + && (conf_wifi_sta_reconnects == 0 + || wifi_sta_reconnects < conf_wifi_sta_reconnects)) { + // No connection or error, but is requested = Still connecting + return MP_OBJ_NEW_SMALL_INT(STAT_CONNECTING); + } else if (wifi_sta_disconn_reason == 0) { + // No activity, No error = Idle + return MP_OBJ_NEW_SMALL_INT(STAT_IDLE); + } else { + // Simply pass the error through from ESP-identifier + return MP_OBJ_NEW_SMALL_INT(wifi_sta_disconn_reason); + } + } + return mp_const_none; + } + + // one argument: return status based on query parameter + switch ((uintptr_t)args[1]) { + case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_stations): { + // return list of connected stations, only if in soft-AP mode + require_if(args[0], WIFI_IF_AP); + wifi_sta_list_t station_list; + esp_exceptions(esp_wifi_ap_get_sta_list(&station_list)); + wifi_sta_info_t *stations = (wifi_sta_info_t *)station_list.sta; + mp_obj_t list = mp_obj_new_list(0, NULL); + for (int i = 0; i < station_list.num; ++i) { + mp_obj_tuple_t *t = mp_obj_new_tuple(1, NULL); + t->items[0] = mp_obj_new_bytes(stations[i].mac, sizeof(stations[i].mac)); + mp_obj_list_append(list, t); + } + return list; + } + case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_rssi): { + // return signal of AP, only in STA mode + require_if(args[0], WIFI_IF_STA); + + wifi_ap_record_t info; + esp_exceptions(esp_wifi_sta_get_ap_info(&info)); + return MP_OBJ_NEW_SMALL_INT(info.rssi); + } + default: + mp_raise_ValueError(MP_ERROR_TEXT("unknown status param")); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_status_obj, 1, 2, network_wlan_status); + +STATIC mp_obj_t network_wlan_scan(mp_obj_t self_in) { + // check that STA mode is active + wifi_mode_t mode; + esp_exceptions(esp_wifi_get_mode(&mode)); + if ((mode & WIFI_MODE_STA) == 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("STA must be active")); + } + + mp_obj_t list = mp_obj_new_list(0, NULL); + wifi_scan_config_t config = { 0 }; + config.show_hidden = true; + MP_THREAD_GIL_EXIT(); + esp_err_t status = esp_wifi_scan_start(&config, 1); + MP_THREAD_GIL_ENTER(); + if (status == 0) { + uint16_t count = 0; + esp_exceptions(esp_wifi_scan_get_ap_num(&count)); + wifi_ap_record_t *wifi_ap_records = calloc(count, sizeof(wifi_ap_record_t)); + esp_exceptions(esp_wifi_scan_get_ap_records(&count, wifi_ap_records)); + for (uint16_t i = 0; i < count; i++) { + mp_obj_tuple_t *t = mp_obj_new_tuple(6, NULL); + uint8_t *x = memchr(wifi_ap_records[i].ssid, 0, sizeof(wifi_ap_records[i].ssid)); + int ssid_len = x ? x - wifi_ap_records[i].ssid : sizeof(wifi_ap_records[i].ssid); + t->items[0] = mp_obj_new_bytes(wifi_ap_records[i].ssid, ssid_len); + t->items[1] = mp_obj_new_bytes(wifi_ap_records[i].bssid, sizeof(wifi_ap_records[i].bssid)); + t->items[2] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].primary); + t->items[3] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].rssi); + t->items[4] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].authmode); + t->items[5] = mp_const_false; // XXX hidden? + mp_obj_list_append(list, MP_OBJ_FROM_PTR(t)); + } + free(wifi_ap_records); + } + return list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_scan_obj, network_wlan_scan); + +STATIC mp_obj_t network_wlan_isconnected(mp_obj_t self_in) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->if_id == WIFI_IF_STA) { + return mp_obj_new_bool(wifi_sta_connected); + } else { + wifi_sta_list_t sta; + esp_wifi_ap_get_sta_list(&sta); + return mp_obj_new_bool(sta.num != 0); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_isconnected_obj, network_wlan_isconnected); + +STATIC mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + bool is_wifi = self->if_id == WIFI_IF_AP || self->if_id == WIFI_IF_STA; + + wifi_config_t cfg; + if (is_wifi) { + esp_exceptions(esp_wifi_get_config(self->if_id, &cfg)); + } + + if (kwargs->used != 0) { + if (!is_wifi) { + goto unknown; + } + + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + int req_if = -1; + + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_mac: { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); + } + esp_exceptions(esp_wifi_set_mac(self->if_id, bufinfo.buf)); + break; + } + case MP_QSTR_essid: { + req_if = WIFI_IF_AP; + size_t len; + const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); + len = MIN(len, sizeof(cfg.ap.ssid)); + memcpy(cfg.ap.ssid, s, len); + cfg.ap.ssid_len = len; + break; + } + case MP_QSTR_hidden: { + req_if = WIFI_IF_AP; + cfg.ap.ssid_hidden = mp_obj_is_true(kwargs->table[i].value); + break; + } + case MP_QSTR_authmode: { + req_if = WIFI_IF_AP; + cfg.ap.authmode = mp_obj_get_int(kwargs->table[i].value); + break; + } + case MP_QSTR_password: { + req_if = WIFI_IF_AP; + size_t len; + const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); + len = MIN(len, sizeof(cfg.ap.password) - 1); + memcpy(cfg.ap.password, s, len); + cfg.ap.password[len] = 0; + break; + } + case MP_QSTR_channel: { + req_if = WIFI_IF_AP; + cfg.ap.channel = mp_obj_get_int(kwargs->table[i].value); + break; + } + case MP_QSTR_dhcp_hostname: { + const char *s = mp_obj_str_get_str(kwargs->table[i].value); + esp_exceptions(tcpip_adapter_set_hostname(self->if_id, s)); + break; + } + case MP_QSTR_max_clients: { + req_if = WIFI_IF_AP; + cfg.ap.max_connection = mp_obj_get_int(kwargs->table[i].value); + break; + } + case MP_QSTR_reconnects: { + int reconnects = mp_obj_get_int(kwargs->table[i].value); + req_if = WIFI_IF_STA; + // parameter reconnects == -1 means to retry forever. + // here means conf_wifi_sta_reconnects == 0 to retry forever. + conf_wifi_sta_reconnects = (reconnects == -1) ? 0 : reconnects + 1; + break; + } + default: + goto unknown; + } + + // We post-check interface requirements to save on code size + if (req_if >= 0) { + require_if(args[0], req_if); + } + } + } + + esp_exceptions(esp_wifi_set_config(self->if_id, &cfg)); + + return mp_const_none; + } + + // Get config + + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } + + int req_if = -1; + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_mac: { + uint8_t mac[6]; + switch (self->if_id) { + case WIFI_IF_AP: // fallthrough intentional + case WIFI_IF_STA: + esp_exceptions(esp_wifi_get_mac(self->if_id, mac)); + return mp_obj_new_bytes(mac, sizeof(mac)); + default: + goto unknown; + } + } + case MP_QSTR_essid: + switch (self->if_id) { + case WIFI_IF_STA: + val = mp_obj_new_str((char *)cfg.sta.ssid, strlen((char *)cfg.sta.ssid)); + break; + case WIFI_IF_AP: + val = mp_obj_new_str((char *)cfg.ap.ssid, cfg.ap.ssid_len); + break; + default: + req_if = WIFI_IF_AP; + } + break; + case MP_QSTR_hidden: + req_if = WIFI_IF_AP; + val = mp_obj_new_bool(cfg.ap.ssid_hidden); + break; + case MP_QSTR_authmode: + req_if = WIFI_IF_AP; + val = MP_OBJ_NEW_SMALL_INT(cfg.ap.authmode); + break; + case MP_QSTR_channel: + req_if = WIFI_IF_AP; + val = MP_OBJ_NEW_SMALL_INT(cfg.ap.channel); + break; + case MP_QSTR_dhcp_hostname: { + const char *s; + esp_exceptions(tcpip_adapter_get_hostname(self->if_id, &s)); + val = mp_obj_new_str(s, strlen(s)); + break; + } + case MP_QSTR_max_clients: { + val = MP_OBJ_NEW_SMALL_INT(cfg.ap.max_connection); + break; + } + case MP_QSTR_reconnects: + req_if = WIFI_IF_STA; + int rec = conf_wifi_sta_reconnects - 1; + val = MP_OBJ_NEW_SMALL_INT(rec); + break; + default: + goto unknown; + } + + // We post-check interface requirements to save on code size + if (req_if >= 0) { + require_if(args[0], req_if); + } + + return val; + +unknown: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); +} +MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_config_obj, 1, network_wlan_config); + +STATIC const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_wlan_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&network_wlan_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_wlan_disconnect_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_wlan_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&network_wlan_scan_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_wlan_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_wlan_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_ifconfig_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); + +STATIC const mp_obj_type_t wlan_if_type = { + { &mp_type_type }, + .name = MP_QSTR_WLAN, + .locals_dict = (mp_obj_t)&wlan_if_locals_dict, +}; + +#endif // MICROPY_PY_NETWORK_WLAN diff --git a/ports/esp32/partitions-16MiB-priv.csv b/ports/esp32/partitions-16MiB-priv.csv deleted file mode 100644 index d36d66c7d4..0000000000 --- a/ports/esp32/partitions-16MiB-priv.csv +++ /dev/null @@ -1,11 +0,0 @@ -# Notes: the offset of the partition table itself is set in -# $IDF_PATH/components/partition_table/Kconfig.projbuild. -# Name, Type, SubType, Offset, Size, Flags -ota_param1, data, 0x91, 0x16000, 0x1000, -ota_param2, data, 0x92, 0x17000, 0x2000, -nvs, data, nvs, 0x19000, 0x6000, -phy_init, data, phy, 0x1f000, 0x1000, -ota0, app, 0x10, 0x20000, 0x500000, -ota1, app, 0x11, 0x520000, 0x300000, -vfs, data, fat, 0x820000, 0x7d0000, -kv, data, 0x83, 0xff0000, 0x10000, diff --git a/ports/esp32/partitions-8MiB.csv b/ports/esp32/partitions-8MiB.csv new file mode 100644 index 0000000000..582d3b50e5 --- /dev/null +++ b/ports/esp32/partitions-8MiB.csv @@ -0,0 +1,7 @@ +# Notes: the offset of the partition table itself is set in +# $IDF_PATH/components/partition_table/Kconfig.projbuild. +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x1F0000, +vfs, data, fat, 0x200000, 0x600000, diff --git a/ports/esp32/partitions.csv b/ports/esp32/partitions.csv index 841815359e..d304dccc53 100644 --- a/ports/esp32/partitions.csv +++ b/ports/esp32/partitions.csv @@ -1,11 +1,7 @@ # Notes: the offset of the partition table itself is set in # $IDF_PATH/components/partition_table/Kconfig.projbuild. -# Name, Type, SubType, Offset, Size, Flags -ota_param1,data,145,0x16000,4K, -ota_param2,data,146,0x17000,8K, -nvs,data,nvs,0x19000,24K, -phy_init,data,phy,0x1f000,4K, -ota0,app,ota_0,0x20000,1984K, -ota1,app,ota_1,0x210000,1088K, -vfs,data,fat,0x320000,832K, -kv,data,131,0x3f0000,64K, +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x180000, +vfs, data, fat, 0x200000, 0x200000, diff --git a/ports/esp32/uart.c b/ports/esp32/uart.c index f19ba934fd..58fc0c6c61 100644 --- a/ports/esp32/uart.c +++ b/ports/esp32/uart.c @@ -33,48 +33,13 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "uart.h" STATIC void uart_irq_handler(void *arg); void uart_init(void) { - uart_config_t uartcfg = { - .baud_rate = MICROPY_HW_UART_REPL_BAUD, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .rx_flow_ctrl_thresh = 0 - }; - uart_param_config(MICROPY_HW_UART_REPL, &uartcfg); - - const uint32_t rxbuf = 129; // IDF requires > 128 min - const uint32_t txbuf = 0; - - uart_driver_install(MICROPY_HW_UART_REPL, rxbuf, txbuf, 0, NULL, 0); - uart_isr_handle_t handle; - uart_isr_free(MICROPY_HW_UART_REPL); - uart_isr_register(MICROPY_HW_UART_REPL, uart_irq_handler, NULL, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, &handle); - uart_enable_rx_intr(MICROPY_HW_UART_REPL); -} - -int uart_stdout_tx_strn(const char *str, size_t len) { - size_t remaining = len; - // TODO add a timeout - for (;;) { - int ret = uart_tx_chars(MICROPY_HW_UART_REPL, str, remaining); - if (ret == -1) { - return -1; - } - remaining -= ret; - if (remaining <= 0) { - break; - } - str += ret; - ulTaskNotifyTake(pdFALSE, 1); - } - return len - remaining; + uart_isr_register(UART_NUM_0, uart_irq_handler, NULL, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, &handle); + uart_enable_rx_intr(UART_NUM_0); } // all code executed in ISR must be in IRAM, and any const data must be in DRAM diff --git a/ports/esp32/uart.h b/ports/esp32/uart.h index 5acd698de3..264c8b8949 100644 --- a/ports/esp32/uart.h +++ b/ports/esp32/uart.h @@ -28,15 +28,6 @@ #ifndef MICROPY_INCLUDED_ESP32_UART_H #define MICROPY_INCLUDED_ESP32_UART_H -#ifndef MICROPY_HW_UART_REPL -#define MICROPY_HW_UART_REPL (UART_NUM_0) -#endif - -#ifndef MICROPY_HW_UART_REPL_BAUD -#define MICROPY_HW_UART_REPL_BAUD (115200) -#endif - void uart_init(void); -int uart_stdout_tx_strn(const char *str, size_t len); #endif // MICROPY_INCLUDED_ESP32_UART_H diff --git a/ports/esp32/usb.c b/ports/esp32/usb.c index 85a7a11c9e..5a613d2441 100644 --- a/ports/esp32/usb.c +++ b/ports/esp32/usb.c @@ -36,6 +36,7 @@ #define CDC_ITF TINYUSB_CDC_ACM_0 static uint8_t usb_rx_buf[CONFIG_USB_CDC_RX_BUFSIZE]; +static uint8_t usb_cdc_connected; static void usb_callback_rx(int itf, cdcacm_event_t *event) { // TODO: what happens if more chars come in during this function, are they lost? @@ -58,6 +59,13 @@ static void usb_callback_rx(int itf, cdcacm_event_t *event) { } } +void usb_callback_line_state_changed(int itf, cdcacm_event_t *event) { + int dtr = event->line_state_changed_data.dtr; + int rts = event->line_state_changed_data.rts; + // If dtr && rts are both true, the CDC is connected to a HOST. + usb_cdc_connected = dtr && rts; +} + void usb_init(void) { // Initialise the USB with defaults. tinyusb_config_t tusb_cfg = {0}; @@ -70,22 +78,21 @@ void usb_init(void) { .rx_unread_buf_sz = 256, .callback_rx = &usb_callback_rx, .callback_rx_wanted_char = NULL, - .callback_line_state_changed = NULL, + .callback_line_state_changed = &usb_callback_line_state_changed, .callback_line_coding_changed = NULL }; + usb_cdc_connected = 0; ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg)); + } void usb_tx_strn(const char *str, size_t len) { - while (len) { - size_t l = len; - if (l > CONFIG_USB_CDC_TX_BUFSIZE) { - l = CONFIG_USB_CDC_TX_BUFSIZE; - } - tinyusb_cdcacm_write_queue(CDC_ITF, (uint8_t *)str, l); - tinyusb_cdcacm_write_flush(CDC_ITF, pdMS_TO_TICKS(1000)); + // Write out the data to the CDC interface, but only while the USB host is connected. + while (usb_cdc_connected && len) { + size_t l = tinyusb_cdcacm_write_queue(CDC_ITF, (uint8_t *)str, len); str += l; len -= l; + tud_cdc_n_write_flush(CDC_ITF); } } diff --git a/ports/esp32/utility.h b/ports/esp32/utility.h deleted file mode 100644 index 57ccda4357..0000000000 --- a/ports/esp32/utility.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef PY_OBJ_UTILITY_H -#define PY_OBJ_UTILITY_H - -#include -#include -#include -#include - -#include "py/builtin.h" -#include "py/gc.h" -#include "py/mperrno.h" -#include "py/obj.h" -#include "py/runtime.h" -#include "py/mpstate.h" - -extern void mp_hal_wake_main_task_from_isr(); - -bool callback_to_python_LoBo(mp_obj_t function, mp_obj_t arg, void *carg); - -mp_obj_t mp_obj_new_strn(const char *data); - -const char *get_str_from_dict(mp_obj_t dict, const char *key); - -int get_int_from_dict(mp_obj_t dict, const char *key); - -#endif diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile new file mode 100644 index 0000000000..d0afe73ea8 --- /dev/null +++ b/ports/esp8266/Makefile @@ -0,0 +1,234 @@ +# Select the board to build for: if not given on the command line, +# then default to GENERIC. +BOARD ?= GENERIC + +# If the build directory is not given, make it reflect the board name. +BUILD ?= build-$(BOARD) + +BOARD_DIR ?= boards/$(BOARD) +ifeq ($(wildcard $(BOARD_DIR)/.),) +$(error Invalid BOARD specified: $(BOARD_DIR)) +endif + +include ../../py/mkenv.mk + +# Optional +-include $(BOARD_DIR)/mpconfigboard.mk + +# qstr definitions (must come before including py.mk) +QSTR_DEFS = qstrdefsport.h #$(BUILD)/pins_qstr.h +QSTR_GLOBAL_DEPENDENCIES = $(BOARD_DIR)/mpconfigboard.h + +# MicroPython feature configurations +MICROPY_ROM_TEXT_COMPRESSION ?= 1 +MICROPY_PY_USSL = 1 +MICROPY_SSL_AXTLS = 1 +AXTLS_DEFS_EXTRA = -Dabort=abort_ -DRT_MAX_PLAIN_LENGTH=1024 -DRT_EXTRA=4096 +BTREE_DEFS_EXTRA = -DDEFPSIZE=1024 -DMINCACHE=3 + +FROZEN_MANIFEST ?= boards/manifest.py + +# include py core make definitions +include $(TOP)/py/py.mk + +GIT_SUBMODULES = lib/axtls lib/berkeley-db-1.xx + +FWBIN = $(BUILD)/firmware-combined.bin +PORT ?= /dev/ttyACM0 +BAUD ?= 115200 +FLASH_MODE ?= qio +FLASH_SIZE ?= detect +CROSS_COMPILE ?= xtensa-lx106-elf- +ESP_SDK = $(shell $(CC) -print-sysroot)/usr + +INC += -I. +INC += -I$(TOP) +INC += -I$(BUILD) +INC += -I$(ESP_SDK)/include + +# UART for "os" messages. 0 is normal UART as used by MicroPython REPL, +# 1 is debug UART (tx only), -1 to disable. +UART_OS = -1 + +CFLAGS_XTENSA = -fsingle-precision-constant -Wdouble-promotion \ + -D__ets__ -DICACHE_FLASH \ + -fno-inline-functions \ + -Wl,-EL -mlongcalls -mtext-section-literals -mforce-l32 \ + -DLWIP_OPEN_SRC + +CFLAGS = $(INC) -Wall -Wpointer-arith -Werror -std=gnu99 -nostdlib -DUART_OS=$(UART_OS) \ + $(CFLAGS_XTENSA) $(CFLAGS_MOD) $(COPT) $(CFLAGS_EXTRA) -I$(BOARD_DIR) + +LD_FILES ?= boards/esp8266_2m.ld +LDFLAGS = -nostdlib -T $(LD_FILES) -Map=$(@:.elf=.map) --cref +LIBS = -L$(ESP_SDK)/lib -lmain -ljson -llwip_open -lpp -lnet80211 -lwpa -lphy -lnet80211 $(LDFLAGS_MOD) + +LIBGCC_FILE_NAME = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) +LIBS += -L$(dir $(LIBGCC_FILE_NAME)) -lgcc + +# Debugging/Optimization +ifeq ($(DEBUG), 1) +CFLAGS += -g +COPT = -O0 +else +CFLAGS += -fdata-sections -ffunction-sections +COPT += -Os -DNDEBUG +LDFLAGS += --gc-sections +endif + +# Options for mpy-cross +MPY_CROSS_FLAGS += -march=xtensa + +SRC_C = \ + strtoll.c \ + main.c \ + help.c \ + esp_mphal.c \ + esp_init_data.c \ + gccollect.c \ + lexerstr32.c \ + uart.c \ + esppwm.c \ + espapa102.c \ + modpyb.c \ + modmachine.c \ + machine_bitstream.c \ + machine_pin.c \ + machine_rtc.c \ + machine_adc.c \ + machine_uart.c \ + machine_wdt.c \ + machine_hspi.c \ + modesp.c \ + modnetwork.c \ + modutime.c \ + moduos.c \ + ets_alt_task.c \ + fatfs_port.c \ + posix_helpers.c \ + hspi.c \ + $(wildcard $(BOARD_DIR)/*.c) \ + $(SRC_MOD) + +EXTMOD_SRC_C = $(addprefix extmod/,\ + modlwip.c \ + modonewire.c \ + ) + +LIB_SRC_C = $(addprefix lib/,\ + libm/math.c \ + libm/fmodf.c \ + libm/nearbyintf.c \ + libm/ef_sqrt.c \ + libm/erf_lgamma.c \ + libm/kf_rem_pio2.c \ + libm/kf_sin.c \ + libm/kf_cos.c \ + libm/kf_tan.c \ + libm/ef_rem_pio2.c \ + libm/sf_erf.c \ + libm/sf_sin.c \ + libm/sf_cos.c \ + libm/sf_tan.c \ + libm/sf_frexp.c \ + libm/sf_modf.c \ + libm/sf_ldexp.c \ + libm/acoshf.c \ + libm/asinfacosf.c \ + libm/asinhf.c \ + libm/atanf.c \ + libm/atanhf.c \ + libm/atan2f.c \ + libm/log1pf.c \ + libm/roundf.c \ + libm/wf_lgamma.c \ + libm/wf_tgamma.c \ + ) + +SHARED_SRC_C = $(addprefix shared/,\ + libc/__errno.c \ + libc/string0.c \ + netutils/netutils.c \ + readline/readline.c \ + runtime/interrupt_char.c \ + runtime/pyexec.c \ + runtime/stdout_helpers.c \ + runtime/sys_stdio_mphal.c \ + timeutils/timeutils.c \ + ) + +DRIVERS_SRC_C = $(addprefix drivers/,\ + bus/softspi.c \ + dht/dht.c \ + ) + +SRC_S = \ + gchelper.s \ + +OBJ = +OBJ += $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) +OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) + +# List of sources for qstr extraction +SRC_QSTR += $(SRC_C) $(EXTMOD_SRC_C) $(SHARED_SRC_C) $(DRIVERS_SRC_C) +# Append any auto-generated sources that are needed by sources listed in SRC_QSTR +SRC_QSTR_AUTO_DEPS += + +all: $(FWBIN) + +CONFVARS_FILE = $(BUILD)/confvars + +ifeq ($(wildcard $(CONFVARS_FILE)),) +$(shell $(MKDIR) -p $(BUILD)) +$(shell echo $(FROZEN_MANIFEST) $(UART_OS) > $(CONFVARS_FILE)) +else ifneq ($(shell cat $(CONFVARS_FILE)), $(FROZEN_MANIFEST) $(UART_OS)) +$(shell echo $(FROZEN_MANIFEST) $(UART_OS) > $(CONFVARS_FILE)) +endif + +$(BUILD)/uart.o: $(CONFVARS_FILE) + +FROZEN_EXTRA_DEPS = $(CONFVARS_FILE) + +ifneq ($(FROZEN_MANIFEST),) +CFLAGS += -DMICROPY_MODULE_FROZEN_MPY +CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool +CFLAGS += -DMICROPY_MODULE_FROZEN_STR +endif + +.PHONY: deploy + +deploy: $(BUILD)/firmware-combined.bin + $(ECHO) "Writing $< to the board" + $(Q)esptool.py --port $(PORT) --baud $(BAUD) write_flash --verify --flash_size=$(FLASH_SIZE) --flash_mode=$(FLASH_MODE) 0 $< + +erase: + $(ECHO) "Erase flash" + $(Q)esptool.py --port $(PORT) --baud $(BAUD) erase_flash + +reset: + echo -e "\r\nimport machine; machine.reset()\r\n" >$(PORT) + +$(FWBIN): $(BUILD)/firmware.elf + $(ECHO) "Create $@" + $(Q)esptool.py elf2image $^ + $(Q)$(PYTHON) makeimg.py $(BUILD)/firmware.elf-0x00000.bin $(BUILD)/firmware.elf-0x[0-5][1-f]000.bin $@ + +$(BUILD)/firmware.elf: $(OBJ) + $(ECHO) "LINK $@" + $(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS) + $(Q)$(SIZE) $@ + +ota: + rm -f $(BUILD)/firmware.elf $(BUILD)/firmware.elf*.bin + $(MAKE) LD_FILES=boards/esp8266_ota.ld FWBIN=$(BUILD)/firmware-ota.bin + +include $(TOP)/py/mkrules.mk + +clean-modules: + git clean -f -d modules + rm -f $(BUILD)/frozen*.c diff --git a/ports/esp8266/README.md b/ports/esp8266/README.md new file mode 100644 index 0000000000..15ca7f891c --- /dev/null +++ b/ports/esp8266/README.md @@ -0,0 +1,188 @@ +MicroPython port to ESP8266 +=========================== + +This is an experimental port of MicroPython for the WiFi modules based +on Espressif ESP8266 chip. + +WARNING: The port is experimental and many APIs are subject to change. + +Supported features include: +- REPL (Python prompt) over UART0. +- Garbage collector, exceptions. +- Unicode support. +- Builtin modules: gc, array, collections, io, struct, sys, esp, network, + many more. +- Arbitrary-precision long integers and 30-bit precision floats. +- WiFi support. +- Sockets using modlwip. +- GPIO and bit-banging I2C, SPI support. +- 1-Wire and WS2812 (aka Neopixel) protocols support. +- Internal filesystem using the flash. +- WebREPL over WiFi from a browser (clients at https://github.com/micropython/webrepl). +- Modules for HTTP, MQTT, many other formats and protocols via + https://github.com/micropython/micropython-lib . + +Documentation is available at http://docs.micropython.org/en/latest/esp8266/quickref.html. + +Build instructions +------------------ + +You need the esp-open-sdk toolchain (which provides both the compiler and libraries), which +you can obtain using one of the following two options: + + - Use a Docker image with a pre-built toolchain (**recommended**). + To use this, install Docker, then prepend + `docker run --rm -v $HOME:$HOME -u $UID -w $PWD larsks/esp-open-sdk ` to the start + of the mpy-cross and firmware `make` commands below. This will run the commands using the + toolchain inside the container but using the files on your local filesystem. + + - or, install the esp-open-sdk directly on your PC, which can be found at + . Clone this repository and + run `make` in its directory to build and install the SDK locally. Make sure + to add toolchain bin directory to your PATH. Read esp-open-sdk's README for + additional important information on toolchain setup. + If you use this approach, then the command below will work exactly. + +Add the external dependencies to the MicroPython repository checkout: +```bash +$ make -C ports/esp8266 submodules +``` +See the README in the repository root for more information about external +dependencies. + +The MicroPython cross-compiler must be built to pre-compile some of the +built-in scripts to bytecode. This can be done using: +```bash +$ make -C mpy-cross +``` +(Prepend the Docker command if using Docker, see above) + +Then, to build MicroPython for the ESP8266, just run: +```bash +$ cd ports/esp8266 +$ make +``` +(Prepend the Docker command if using Docker, see above) + +This will produce binary images in the `build-GENERIC/` subdirectory. If you +install MicroPython to your module for the first time, or after installing any +other firmware, you should erase flash completely: +```bash +$ esptool.py --port /dev/ttyXXX erase_flash +``` + +You can install esptool.py either from your system package manager or from PyPi. + +Erasing the flash is also useful as a troubleshooting measure, if a module doesn't +behave as expected. + +To flash MicroPython image to your ESP8266, use: +```bash +$ make deploy +``` +(This should not be run inside Docker as it will need access to the serial port.) + +This will use the `esptool.py` script to download the images. You must have +your ESP module in the bootloader mode, and connected to a serial port on your PC. +The default serial port is `/dev/ttyACM0`, flash mode is `qio` and flash size is +`detect` (auto-detect based on Flash ID). + +To specify other values for `esptool.py`, use, e.g.: +```bash +$ make PORT=/dev/ttyUSB0 FLASH_MODE=qio FLASH_SIZE=32m deploy +``` +(note that flash size is in megabits) + +If you want to flash manually using `esptool.py` directly, the image produced is +`build-GENERIC/firmware-combined.bin`, to be flashed at 0x00000. + +The default board definition is the directory `boards/GENERIC`. +For a custom configuration you can define your own board in the directory `boards/`. + +The `BOARD` variable can be set on the make command line, for example: +```bash +$ make BOARD=GENERIC_512K +``` + +__512KB FlashROM version__ + +The normal build described above requires modules with at least 1MB of FlashROM +onboard. There's a special configuration for 512KB modules, which can be +built with `make BOARD=GENERIC_512K`. This configuration is highly limited, lacks +filesystem support, WebREPL, and has many other features disabled. It's mostly +suitable for advanced users who are interested to fine-tune options to achieve a +required setup. If you are an end user, please consider using a module with at +least 1MB of FlashROM. + +First start +----------- + +Be sure to change ESP8266's WiFi access point password ASAP, see below. + +__Serial prompt__ + +You can access the REPL (Python prompt) over UART (the same as used for +programming). +- Baudrate: 115200 + +Run `help()` for some basic information. + +__WiFi__ + +Initially, the device configures itself as a WiFi access point (AP). +- ESSID: MicroPython-xxxxxx (x’s are replaced with part of the MAC address). +- Password: micropythoN (note the upper-case N). +- IP address of the board: 192.168.4.1. +- DHCP-server is activated. +- Please be sure to change the password to something non-guessable + immediately. `help()` gives information how. + +__WebREPL__ + +Python prompt over WiFi, connecting through a browser. +- Hosted at http://micropython.org/webrepl. +- GitHub repository https://github.com/micropython/webrepl. + Please follow the instructions there. + +__upip__ + +The ESP8266 port comes with builtin `upip` package manager, which can +be used to install additional modules (see the main README for more +information): + +``` +>>> import upip +>>> upip.install("micropython-pystone_lowmem") +[...] +>>> import pystone_lowmem +>>> pystone_lowmem.main() +``` + +Downloading and installing packages may requite a lot of free memory, +if you get an error, retry immediately after the hard reset. + +Documentation +------------- + +More detailed documentation and instructions can be found at +http://docs.micropython.org/en/latest/esp8266/ , which includes Quick +Reference, Tutorial, General Information related to ESP8266 port, and +to MicroPython in general. + +Troubleshooting +--------------- + +While the port is in beta, it's known to be generally stable. If you +experience strange bootloops, crashes, lockups, here's a list to check against: + +- You didn't erase flash before programming MicroPython firmware. +- Firmware can be occasionally flashed incorrectly. Just retry. Recent + esptool.py versions have --verify option. +- Power supply you use doesn't provide enough power for ESP8266 or isn't + stable enough. +- A module/flash may be defective (not unheard of for cheap modules). + +Please consult dedicated ESP8266 forums/resources for hardware-related +problems. + +Additional information may be available by the documentation links above. diff --git a/ports/esp8266/boards/GENERIC/board.json b/ports/esp8266/boards/GENERIC/board.json new file mode 100644 index 0000000000..46c2659d8a --- /dev/null +++ b/ports/esp8266/boards/GENERIC/board.json @@ -0,0 +1,19 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "WiFi" + ], + "id": "esp8266", + "images": [], + "mcu": "esp8266", + "product": "ESP8266 with 2MiB+ flash", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "variants": { + "ota": "OTA compatible" + }, + "vendor": "Espressif" +} diff --git a/ports/esp8266/boards/GENERIC/board.md b/ports/esp8266/boards/GENERIC/board.md new file mode 100644 index 0000000000..b93ca509f8 --- /dev/null +++ b/ports/esp8266/boards/GENERIC/board.md @@ -0,0 +1,19 @@ +The following are daily builds of the ESP8266 firmware for boards with at +least 2MiB of flash. They have the latest features and bug fixes, WebREPL is +not automatically started, and debugging is enabled by default. + +Note: v1.12-334 and newer (including v1.13) require an ESP8266 module with +2MiB of flash or more, and use littlefs as the filesystem by default. When +upgrading from older firmware please backup your files first, and either +erase all flash before upgrading, or after upgrading execute +`uos.VfsLfs2.mkfs(bdev)`. + +### OTA builds +Over-The-Air (OTA) builds of the ESP8266 firmware are also provided. + +The first time you use this build you need to flash one of the "initial image" +images using esptool.py as described above. After that, you can update the +firmware over the air using the "OTA update" file in conjunction with the +ota-client script from yaota8266. The "OTA update" files are digitally signed +and will only work with the provided "initial image" files, and vice versa. +(Note: this feature is work-in-progress.) diff --git a/ports/esp8266/boards/GENERIC/manifest.py b/ports/esp8266/boards/GENERIC/manifest.py new file mode 100644 index 0000000000..9ce3ffe3aa --- /dev/null +++ b/ports/esp8266/boards/GENERIC/manifest.py @@ -0,0 +1,21 @@ +# base modules +include("$(PORT_DIR)/boards/manifest.py") + +# uasyncio +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") + +# drivers +freeze("$(MPY_DIR)/drivers/display", "ssd1306.py") + +# Libraries from micropython-lib, include only if the library directory exists +if os.path.isdir(convert_path("$(MPY_LIB_DIR)")): + # file utilities + freeze("$(MPY_LIB_DIR)/micropython/upysh", "upysh.py") + + # requests + freeze("$(MPY_LIB_DIR)/python-ecosys/urequests", "urequests.py") + freeze("$(MPY_LIB_DIR)/micropython/urllib.urequest", "urllib/urequest.py") + + # umqtt + freeze("$(MPY_LIB_DIR)/micropython/umqtt.simple", "umqtt/simple.py") + freeze("$(MPY_LIB_DIR)/micropython/umqtt.robust", "umqtt/robust.py") diff --git a/ports/esp8266/boards/GENERIC/mpconfigboard.h b/ports/esp8266/boards/GENERIC/mpconfigboard.h new file mode 100644 index 0000000000..e4991ba970 --- /dev/null +++ b/ports/esp8266/boards/GENERIC/mpconfigboard.h @@ -0,0 +1,22 @@ +#define MICROPY_HW_BOARD_NAME "ESP module" +#define MICROPY_HW_MCU_NAME "ESP8266" + +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_EMIT_XTENSA (1) +#define MICROPY_EMIT_INLINE_XTENSA (1) + +#define MICROPY_DEBUG_PRINTERS (1) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) + +#define MICROPY_READER_VFS (MICROPY_VFS) +#define MICROPY_VFS (1) + +#define MICROPY_PY_FSTRINGS (1) +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_SYS_STDIO_BUFFER (1) +#define MICROPY_PY_UASYNCIO (1) +#define MICROPY_PY_URE_SUB (1) +#define MICROPY_PY_UCRYPTOLIB (1) +#define MICROPY_PY_FRAMEBUF (1) diff --git a/ports/esp8266/boards/GENERIC/mpconfigboard.mk b/ports/esp8266/boards/GENERIC/mpconfigboard.mk new file mode 100644 index 0000000000..6861317218 --- /dev/null +++ b/ports/esp8266/boards/GENERIC/mpconfigboard.mk @@ -0,0 +1,7 @@ +LD_FILES = boards/esp8266_2m.ld + +MICROPY_PY_BTREE ?= 1 +MICROPY_VFS_FAT ?= 1 +MICROPY_VFS_LFS2 ?= 1 + +FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py diff --git a/ports/esp8266/boards/GENERIC_1M/board.json b/ports/esp8266/boards/GENERIC_1M/board.json new file mode 100644 index 0000000000..2ef4bd99fa --- /dev/null +++ b/ports/esp8266/boards/GENERIC_1M/board.json @@ -0,0 +1,16 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "WiFi" + ], + "id": "esp8266-1m", + "images": [], + "mcu": "esp8266", + "product": "ESP8266 with 1MiB flash", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp8266/boards/GENERIC_1M/board.md b/ports/esp8266/boards/GENERIC_1M/board.md new file mode 100644 index 0000000000..4a0e677078 --- /dev/null +++ b/ports/esp8266/boards/GENERIC_1M/board.md @@ -0,0 +1,5 @@ +The following are daily builds of the ESP8266 firmware tailored for modules with +only 1MiB of flash. This firmware uses littlefs as the filesystem. +When upgrading from older firmware that uses a FAT filesystem please backup your files +first, and either erase all flash before upgrading, or after upgrading execute +`uos.VfsLfs2.mkfs(bdev)`. diff --git a/ports/esp8266/boards/GENERIC_1M/mpconfigboard.h b/ports/esp8266/boards/GENERIC_1M/mpconfigboard.h new file mode 100644 index 0000000000..ed50729936 --- /dev/null +++ b/ports/esp8266/boards/GENERIC_1M/mpconfigboard.h @@ -0,0 +1,20 @@ +#define MICROPY_HW_BOARD_NAME "ESP module (1M)" +#define MICROPY_HW_MCU_NAME "ESP8266" + +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_EMIT_XTENSA (1) +#define MICROPY_EMIT_INLINE_XTENSA (1) + +#define MICROPY_DEBUG_PRINTERS (1) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) + +#define MICROPY_READER_VFS (MICROPY_VFS) +#define MICROPY_VFS (1) + +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_SYS_STDIO_BUFFER (1) +#define MICROPY_PY_URE_SUB (1) +#define MICROPY_PY_UCRYPTOLIB (1) +#define MICROPY_PY_FRAMEBUF (1) diff --git a/ports/esp8266/boards/GENERIC_1M/mpconfigboard.mk b/ports/esp8266/boards/GENERIC_1M/mpconfigboard.mk new file mode 100644 index 0000000000..fdbb0d8245 --- /dev/null +++ b/ports/esp8266/boards/GENERIC_1M/mpconfigboard.mk @@ -0,0 +1,4 @@ +LD_FILES = boards/esp8266_1m.ld + +MICROPY_PY_BTREE ?= 1 +MICROPY_VFS_LFS2 ?= 1 diff --git a/ports/esp8266/boards/GENERIC_512K/_boot.py b/ports/esp8266/boards/GENERIC_512K/_boot.py new file mode 100644 index 0000000000..1a55cfd36c --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/_boot.py @@ -0,0 +1,3 @@ +import gc + +gc.threshold((gc.mem_free() + gc.mem_alloc()) // 4) diff --git a/ports/esp8266/boards/GENERIC_512K/board.json b/ports/esp8266/boards/GENERIC_512K/board.json new file mode 100644 index 0000000000..10050592cf --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/board.json @@ -0,0 +1,16 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "WiFi" + ], + "id": "esp8266-512k", + "images": [], + "mcu": "esp8266", + "product": "ESP8266 with 512kiB flash", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "Espressif" +} diff --git a/ports/esp8266/boards/GENERIC_512K/board.md b/ports/esp8266/boards/GENERIC_512K/board.md new file mode 100644 index 0000000000..1f6e2c7907 --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/board.md @@ -0,0 +1,3 @@ +The following are daily builds of the ESP8266 firmware tailored for modules with +only 512kiB of flash. Certain features are disabled to get the firmware down +to this size. diff --git a/ports/esp8266/boards/GENERIC_512K/manifest.py b/ports/esp8266/boards/GENERIC_512K/manifest.py new file mode 100644 index 0000000000..ee148c8089 --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/manifest.py @@ -0,0 +1,6 @@ +freeze("$(BOARD_DIR)", "_boot.py", opt=3) +freeze("$(PORT_DIR)/modules", ("apa102.py", "ntptime.py", "port_diag.py")) +freeze("$(MPY_DIR)/drivers/dht", "dht.py") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/webrepl/manifest.py") +include("$(MPY_DIR)/drivers/neopixel/manifest.py") diff --git a/ports/esp8266/boards/GENERIC_512K/mpconfigboard.h b/ports/esp8266/boards/GENERIC_512K/mpconfigboard.h new file mode 100644 index 0000000000..0693232aa1 --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/mpconfigboard.h @@ -0,0 +1,4 @@ +#define MICROPY_HW_BOARD_NAME "ESP module (512K)" +#define MICROPY_HW_MCU_NAME "ESP8266" + +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) diff --git a/ports/esp8266/boards/GENERIC_512K/mpconfigboard.mk b/ports/esp8266/boards/GENERIC_512K/mpconfigboard.mk new file mode 100644 index 0000000000..120351909b --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/mpconfigboard.mk @@ -0,0 +1,3 @@ +LD_FILES = boards/esp8266_512k.ld + +FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py diff --git a/ports/esp8266/boards/deploy.md b/ports/esp8266/boards/deploy.md new file mode 100644 index 0000000000..520316367c --- /dev/null +++ b/ports/esp8266/boards/deploy.md @@ -0,0 +1 @@ +Program your board using the esptool.py program as described [the tutorial](http://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/intro.html#deploying-the-firmware). diff --git a/ports/esp8266/boards/eagle.rom.addr.v6.ld b/ports/esp8266/boards/eagle.rom.addr.v6.ld new file mode 100644 index 0000000000..1b3ce55d01 --- /dev/null +++ b/ports/esp8266/boards/eagle.rom.addr.v6.ld @@ -0,0 +1,351 @@ +PROVIDE ( Cache_Read_Disable = 0x400047f0 ); +PROVIDE ( Cache_Read_Enable = 0x40004678 ); +PROVIDE ( FilePacketSendReqMsgProc = 0x400035a0 ); +PROVIDE ( FlashDwnLdParamCfgMsgProc = 0x4000368c ); +PROVIDE ( FlashDwnLdStartMsgProc = 0x40003538 ); +PROVIDE ( FlashDwnLdStopReqMsgProc = 0x40003658 ); +PROVIDE ( GetUartDevice = 0x40003f4c ); +PROVIDE ( MD5Final = 0x40009900 ); +PROVIDE ( MD5Init = 0x40009818 ); +PROVIDE ( MD5Update = 0x40009834 ); +PROVIDE ( MemDwnLdStartMsgProc = 0x400036c4 ); +PROVIDE ( MemDwnLdStopReqMsgProc = 0x4000377c ); +PROVIDE ( MemPacketSendReqMsgProc = 0x400036f0 ); +PROVIDE ( RcvMsg = 0x40003eac ); +PROVIDE ( SHA1Final = 0x4000b648 ); +PROVIDE ( SHA1Init = 0x4000b584 ); +PROVIDE ( SHA1Transform = 0x4000a364 ); +PROVIDE ( SHA1Update = 0x4000b5a8 ); +PROVIDE ( SPI_read_status = 0x400043c8 ); +PROVIDE ( SPI_write_status = 0x40004400 ); +PROVIDE ( SPI_write_enable = 0x4000443c ); +PROVIDE ( Wait_SPI_Idle = 0x4000448c ); +PROVIDE ( Enable_QMode = 0x400044c0 ); +PROVIDE ( SPIEraseArea = 0x40004b44 ); +PROVIDE ( SPIEraseBlock = 0x400049b4 ); +PROVIDE ( SPIEraseChip = 0x40004984 ); +PROVIDE ( SPIEraseSector = 0x40004a00 ); +PROVIDE ( SPILock = 0x400048a8 ); +PROVIDE ( SPIParamCfg = 0x40004c2c ); +PROVIDE ( SPIRead = 0x40004b1c ); +PROVIDE ( SPIReadModeCnfig = 0x400048ec ); +PROVIDE ( SPIUnlock = 0x40004878 ); +PROVIDE ( SPIWrite = 0x40004a4c ); +PROVIDE ( SelectSpiFunction = 0x40003f58 ); +PROVIDE ( SendMsg = 0x40003cf4 ); +PROVIDE ( UartConnCheck = 0x40003230 ); +PROVIDE ( UartConnectProc = 0x400037a0 ); +PROVIDE ( UartDwnLdProc = 0x40003368 ); +PROVIDE ( UartGetCmdLn = 0x40003ef4 ); +PROVIDE ( UartRegReadProc = 0x4000381c ); +PROVIDE ( UartRegWriteProc = 0x400037ac ); +PROVIDE ( UartRxString = 0x40003c30 ); +PROVIDE ( Uart_Init = 0x40003a14 ); +PROVIDE ( _DebugExceptionVector = 0x40000010 ); +PROVIDE ( _DoubleExceptionVector = 0x40000070 ); +PROVIDE ( _KernelExceptionVector = 0x40000030 ); +PROVIDE ( _NMIExceptionVector = 0x40000020 ); +PROVIDE ( _ResetHandler = 0x400000a4 ); +PROVIDE ( _ResetVector = 0x40000080 ); +PROVIDE ( _UserExceptionVector = 0x40000050 ); +__adddf3 = 0x4000c538; +__addsf3 = 0x4000c180; +__divdf3 = 0x4000cb94; +__divdi3 = 0x4000ce60; +__divsi3 = 0x4000dc88; +__extendsfdf2 = 0x4000cdfc; +__fixdfsi = 0x4000ccb8; +__fixunsdfsi = 0x4000cd00; +__fixunssfsi = 0x4000c4c4; +__floatsidf = 0x4000e2f0; +__floatsisf = 0x4000e2ac; +__floatunsidf = 0x4000e2e8; +__floatunsisf = 0x4000e2a4; +__muldf3 = 0x4000c8f0; +__muldi3 = 0x40000650; +__mulsf3 = 0x4000c3dc; +__subdf3 = 0x4000c688; +__subsf3 = 0x4000c268; +__truncdfsf2 = 0x4000cd5c; +__udivdi3 = 0x4000d310; +__udivsi3 = 0x4000e21c; +__umoddi3 = 0x4000d770; +__umodsi3 = 0x4000e268; +__umulsidi3 = 0x4000dcf0; +PROVIDE ( _rom_store = 0x4000e388 ); +PROVIDE ( _rom_store_table = 0x4000e328 ); +PROVIDE ( _start = 0x4000042c ); +PROVIDE ( _xtos_alloca_handler = 0x4000dbe0 ); +PROVIDE ( _xtos_c_wrapper_handler = 0x40000598 ); +PROVIDE ( _xtos_cause3_handler = 0x40000590 ); +PROVIDE ( _xtos_ints_off = 0x4000bda4 ); +PROVIDE ( _xtos_ints_on = 0x4000bd84 ); +PROVIDE ( _xtos_l1int_handler = 0x4000048c ); +PROVIDE ( _xtos_p_none = 0x4000dbf8 ); +PROVIDE ( _xtos_restore_intlevel = 0x4000056c ); +PROVIDE ( _xtos_return_from_exc = 0x4000dc54 ); +PROVIDE ( _xtos_set_exception_handler = 0x40000454 ); +PROVIDE ( _xtos_set_interrupt_handler = 0x4000bd70 ); +PROVIDE ( _xtos_set_interrupt_handler_arg = 0x4000bd28 ); +PROVIDE ( _xtos_set_intlevel = 0x4000dbfc ); +PROVIDE ( _xtos_set_min_intlevel = 0x4000dc18 ); +PROVIDE ( _xtos_set_vpri = 0x40000574 ); +PROVIDE ( _xtos_syscall_handler = 0x4000dbe4 ); +PROVIDE ( _xtos_unhandled_exception = 0x4000dc44 ); +PROVIDE ( _xtos_unhandled_interrupt = 0x4000dc3c ); +PROVIDE ( aes_decrypt = 0x400092d4 ); +PROVIDE ( aes_decrypt_deinit = 0x400092e4 ); +PROVIDE ( aes_decrypt_init = 0x40008ea4 ); +PROVIDE ( aes_unwrap = 0x40009410 ); +PROVIDE ( base64_decode = 0x40009648 ); +PROVIDE ( base64_encode = 0x400094fc ); +PROVIDE ( bzero = 0x4000de84 ); +PROVIDE ( cmd_parse = 0x40000814 ); +PROVIDE ( conv_str_decimal = 0x40000b24 ); +PROVIDE ( conv_str_hex = 0x40000cb8 ); +PROVIDE ( convert_para_str = 0x40000a60 ); +PROVIDE ( dtm_get_intr_mask = 0x400026d0 ); +PROVIDE ( dtm_params_init = 0x4000269c ); +PROVIDE ( dtm_set_intr_mask = 0x400026c8 ); +PROVIDE ( dtm_set_params = 0x400026dc ); +PROVIDE ( eprintf = 0x40001d14 ); +PROVIDE ( eprintf_init_buf = 0x40001cb8 ); +PROVIDE ( eprintf_to_host = 0x40001d48 ); +PROVIDE ( est_get_printf_buf_remain_len = 0x40002494 ); +PROVIDE ( est_reset_printf_buf_len = 0x4000249c ); +PROVIDE ( ets_bzero = 0x40002ae8 ); +PROVIDE ( ets_char2xdigit = 0x40002b74 ); +PROVIDE ( ets_delay_us = 0x40002ecc ); +PROVIDE ( ets_enter_sleep = 0x400027b8 ); +PROVIDE ( ets_external_printf = 0x40002578 ); +PROVIDE ( ets_get_cpu_frequency = 0x40002f0c ); +PROVIDE ( ets_getc = 0x40002bcc ); +PROVIDE ( ets_install_external_printf = 0x40002450 ); +PROVIDE ( ets_install_putc1 = 0x4000242c ); +PROVIDE ( ets_install_putc2 = 0x4000248c ); +PROVIDE ( ets_install_uart_printf = 0x40002438 ); +PROVIDE ( ets_intr_lock = 0x40000f74 ); +PROVIDE ( ets_intr_unlock = 0x40000f80 ); +PROVIDE ( ets_isr_attach = 0x40000f88 ); +PROVIDE ( ets_isr_mask = 0x40000f98 ); +PROVIDE ( ets_isr_unmask = 0x40000fa8 ); +PROVIDE ( ets_memcmp = 0x400018d4 ); +PROVIDE ( ets_memcpy = 0x400018b4 ); +PROVIDE ( ets_memmove = 0x400018c4 ); +PROVIDE ( ets_memset = 0x400018a4 ); +PROVIDE ( _ets_post = 0x40000e24 ); +PROVIDE ( ets_printf = 0x400024cc ); +PROVIDE ( ets_putc = 0x40002be8 ); +PROVIDE ( ets_rtc_int_register = 0x40002a40 ); +PROVIDE ( _ets_run = 0x40000e04 ); +PROVIDE ( _ets_set_idle_cb = 0x40000dc0 ); +PROVIDE ( ets_set_user_start = 0x40000fbc ); +PROVIDE ( ets_str2macaddr = 0x40002af8 ); +PROVIDE ( ets_strcmp = 0x40002aa8 ); +PROVIDE ( ets_strcpy = 0x40002a88 ); +PROVIDE ( ets_strlen = 0x40002ac8 ); +PROVIDE ( ets_strncmp = 0x40002ab8 ); +PROVIDE ( ets_strncpy = 0x40002a98 ); +PROVIDE ( ets_strstr = 0x40002ad8 ); +PROVIDE ( _ets_task = 0x40000dd0 ); +PROVIDE ( ets_timer_arm = 0x40002cc4 ); +PROVIDE ( ets_timer_disarm = 0x40002d40 ); +PROVIDE ( ets_timer_done = 0x40002d80 ); +PROVIDE ( ets_timer_handler_isr = 0x40002da8 ); +PROVIDE ( _ets_timer_init = 0x40002e68 ); +PROVIDE ( ets_timer_setfn = 0x40002c48 ); +PROVIDE ( ets_uart_printf = 0x40002544 ); +PROVIDE ( ets_update_cpu_frequency = 0x40002f04 ); +PROVIDE ( ets_vprintf = 0x40001f00 ); +PROVIDE ( ets_wdt_disable = 0x400030f0 ); +PROVIDE ( ets_wdt_enable = 0x40002fa0 ); +PROVIDE ( ets_wdt_get_mode = 0x40002f34 ); +PROVIDE ( ets_wdt_init = 0x40003170 ); +PROVIDE ( ets_wdt_restore = 0x40003158 ); +PROVIDE ( ets_write_char = 0x40001da0 ); +PROVIDE ( get_first_seg = 0x4000091c ); +PROVIDE ( gpio_init = 0x40004c50 ); +PROVIDE ( gpio_input_get = 0x40004cf0 ); +PROVIDE ( gpio_intr_ack = 0x40004dcc ); +PROVIDE ( gpio_intr_handler_register = 0x40004e28 ); +PROVIDE ( gpio_intr_pending = 0x40004d88 ); +PROVIDE ( gpio_intr_test = 0x40004efc ); +PROVIDE ( gpio_output_set = 0x40004cd0 ); +PROVIDE ( gpio_pin_intr_state_set = 0x40004d90 ); +PROVIDE ( gpio_pin_wakeup_disable = 0x40004ed4 ); +PROVIDE ( gpio_pin_wakeup_enable = 0x40004e90 ); +PROVIDE ( gpio_register_get = 0x40004d5c ); +PROVIDE ( gpio_register_set = 0x40004d04 ); +PROVIDE ( hmac_md5 = 0x4000a2cc ); +PROVIDE ( hmac_md5_vector = 0x4000a160 ); +PROVIDE ( hmac_sha1 = 0x4000ba28 ); +PROVIDE ( hmac_sha1_vector = 0x4000b8b4 ); +PROVIDE ( lldesc_build_chain = 0x40004f40 ); +PROVIDE ( lldesc_num2link = 0x40005050 ); +PROVIDE ( lldesc_set_owner = 0x4000507c ); +PROVIDE ( main = 0x40000fec ); +PROVIDE ( md5_vector = 0x400097ac ); +PROVIDE ( mem_calloc = 0x40001c2c ); +PROVIDE ( mem_free = 0x400019e0 ); +PROVIDE ( mem_init = 0x40001998 ); +PROVIDE ( mem_malloc = 0x40001b40 ); +PROVIDE ( mem_realloc = 0x40001c6c ); +PROVIDE ( mem_trim = 0x40001a14 ); +PROVIDE ( mem_zalloc = 0x40001c58 ); +PROVIDE ( memcmp = 0x4000dea8 ); +PROVIDE ( memcpy = 0x4000df48 ); +PROVIDE ( memmove = 0x4000e04c ); +PROVIDE ( memset = 0x4000e190 ); +PROVIDE ( multofup = 0x400031c0 ); +PROVIDE ( pbkdf2_sha1 = 0x4000b840 ); +PROVIDE ( phy_get_romfuncs = 0x40006b08 ); +PROVIDE ( rand = 0x40000600 ); +PROVIDE ( rc4_skip = 0x4000dd68 ); +PROVIDE ( recv_packet = 0x40003d08 ); +PROVIDE ( remove_head_space = 0x40000a04 ); +PROVIDE ( rijndaelKeySetupDec = 0x40008dd0 ); +PROVIDE ( rijndaelKeySetupEnc = 0x40009300 ); +PROVIDE ( rom_abs_temp = 0x400060c0 ); +PROVIDE ( rom_ana_inf_gating_en = 0x40006b10 ); +PROVIDE ( rom_cal_tos_v50 = 0x40007a28 ); +PROVIDE ( rom_chip_50_set_channel = 0x40006f84 ); +PROVIDE ( rom_chip_v5_disable_cca = 0x400060d0 ); +PROVIDE ( rom_chip_v5_enable_cca = 0x400060ec ); +PROVIDE ( rom_chip_v5_rx_init = 0x4000711c ); +PROVIDE ( rom_chip_v5_sense_backoff = 0x4000610c ); +PROVIDE ( rom_chip_v5_tx_init = 0x4000718c ); +PROVIDE ( rom_dc_iq_est = 0x4000615c ); +PROVIDE ( rom_en_pwdet = 0x400061b8 ); +PROVIDE ( rom_get_bb_atten = 0x40006238 ); +PROVIDE ( rom_get_corr_power = 0x40006260 ); +PROVIDE ( rom_get_fm_sar_dout = 0x400062dc ); +PROVIDE ( rom_get_noisefloor = 0x40006394 ); +PROVIDE ( rom_get_power_db = 0x400063b0 ); +PROVIDE ( rom_i2c_readReg = 0x40007268 ); +PROVIDE ( rom_i2c_readReg_Mask = 0x4000729c ); +PROVIDE ( rom_i2c_writeReg = 0x400072d8 ); +PROVIDE ( rom_i2c_writeReg_Mask = 0x4000730c ); +PROVIDE ( rom_iq_est_disable = 0x40006400 ); +PROVIDE ( rom_iq_est_enable = 0x40006430 ); +PROVIDE ( rom_linear_to_db = 0x40006484 ); +PROVIDE ( rom_mhz2ieee = 0x400065a4 ); +PROVIDE ( rom_pbus_dco___SA2 = 0x40007bf0 ); +PROVIDE ( rom_pbus_debugmode = 0x4000737c ); +PROVIDE ( rom_pbus_enter_debugmode = 0x40007410 ); +PROVIDE ( rom_pbus_exit_debugmode = 0x40007448 ); +PROVIDE ( rom_pbus_force_test = 0x4000747c ); +PROVIDE ( rom_pbus_rd = 0x400074d8 ); +PROVIDE ( rom_pbus_set_rxgain = 0x4000754c ); +PROVIDE ( rom_pbus_set_txgain = 0x40007610 ); +PROVIDE ( rom_pbus_workmode = 0x40007648 ); +PROVIDE ( rom_pbus_xpd_rx_off = 0x40007688 ); +PROVIDE ( rom_pbus_xpd_rx_on = 0x400076cc ); +PROVIDE ( rom_pbus_xpd_tx_off = 0x400076fc ); +PROVIDE ( rom_pbus_xpd_tx_on = 0x40007740 ); +PROVIDE ( rom_pbus_xpd_tx_on__low_gain = 0x400077a0 ); +PROVIDE ( rom_phy_reset_req = 0x40007804 ); +PROVIDE ( rom_restart_cal = 0x4000781c ); +PROVIDE ( rom_rfcal_pwrctrl = 0x40007eb4 ); +PROVIDE ( rom_rfcal_rxiq = 0x4000804c ); +PROVIDE ( rom_rfcal_rxiq_set_reg = 0x40008264 ); +PROVIDE ( rom_rfcal_txcap = 0x40008388 ); +PROVIDE ( rom_rfcal_txiq = 0x40008610 ); +PROVIDE ( rom_rfcal_txiq_cover = 0x400088b8 ); +PROVIDE ( rom_rfcal_txiq_set_reg = 0x40008a70 ); +PROVIDE ( rom_rfpll_reset = 0x40007868 ); +PROVIDE ( rom_rfpll_set_freq = 0x40007968 ); +PROVIDE ( rom_rxiq_cover_mg_mp = 0x40008b6c ); +PROVIDE ( rom_rxiq_get_mis = 0x40006628 ); +PROVIDE ( rom_sar_init = 0x40006738 ); +PROVIDE ( rom_set_ana_inf_tx_scale = 0x4000678c ); +PROVIDE ( rom_set_channel_freq = 0x40006c50 ); +PROVIDE ( rom_set_loopback_gain = 0x400067c8 ); +PROVIDE ( rom_set_noise_floor = 0x40006830 ); +PROVIDE ( rom_set_rxclk_en = 0x40006550 ); +PROVIDE ( rom_set_txbb_atten = 0x40008c6c ); +PROVIDE ( rom_set_txclk_en = 0x4000650c ); +PROVIDE ( rom_set_txiq_cal = 0x40008d34 ); +PROVIDE ( rom_start_noisefloor = 0x40006874 ); +PROVIDE ( rom_start_tx_tone = 0x400068b4 ); +PROVIDE ( rom_stop_tx_tone = 0x4000698c ); +PROVIDE ( rom_tx_mac_disable = 0x40006a98 ); +PROVIDE ( rom_tx_mac_enable = 0x40006ad4 ); +PROVIDE ( rom_txtone_linear_pwr = 0x40006a1c ); +PROVIDE ( rom_write_rfpll_sdm = 0x400078dc ); +PROVIDE ( roundup2 = 0x400031b4 ); +PROVIDE ( rtc_enter_sleep = 0x40002870 ); +PROVIDE ( rtc_get_reset_reason = 0x400025e0 ); +PROVIDE ( rtc_intr_handler = 0x400029ec ); +PROVIDE ( rtc_set_sleep_mode = 0x40002668 ); +PROVIDE ( save_rxbcn_mactime = 0x400027a4 ); +PROVIDE ( save_tsf_us = 0x400027ac ); +PROVIDE ( send_packet = 0x40003c80 ); +PROVIDE ( sha1_prf = 0x4000ba48 ); +PROVIDE ( sha1_vector = 0x4000a2ec ); +PROVIDE ( sip_alloc_to_host_evt = 0x40005180 ); +PROVIDE ( sip_get_ptr = 0x400058a8 ); +PROVIDE ( sip_get_state = 0x40005668 ); +PROVIDE ( sip_init_attach = 0x4000567c ); +PROVIDE ( sip_install_rx_ctrl_cb = 0x4000544c ); +PROVIDE ( sip_install_rx_data_cb = 0x4000545c ); +PROVIDE ( sip_post = 0x400050fc ); +PROVIDE ( sip_post_init = 0x400056c4 ); +PROVIDE ( sip_reclaim_from_host_cmd = 0x4000534c ); +PROVIDE ( sip_reclaim_tx_data_pkt = 0x400052c0 ); +PROVIDE ( sip_send = 0x40005808 ); +PROVIDE ( sip_to_host_chain_append = 0x40005864 ); +PROVIDE ( sip_to_host_evt_send_done = 0x40005234 ); +PROVIDE ( slc_add_credits = 0x400060ac ); +PROVIDE ( slc_enable = 0x40005d90 ); +PROVIDE ( slc_from_host_chain_fetch = 0x40005f24 ); +PROVIDE ( slc_from_host_chain_recycle = 0x40005e94 ); +PROVIDE ( slc_init_attach = 0x40005c50 ); +PROVIDE ( slc_init_credit = 0x4000608c ); +PROVIDE ( slc_pause_from_host = 0x40006014 ); +PROVIDE ( slc_reattach = 0x40005c1c ); +PROVIDE ( slc_resume_from_host = 0x4000603c ); +PROVIDE ( slc_select_tohost_gpio = 0x40005dc0 ); +PROVIDE ( slc_select_tohost_gpio_mode = 0x40005db8 ); +PROVIDE ( slc_send_to_host_chain = 0x40005de4 ); +PROVIDE ( slc_set_host_io_max_window = 0x40006068 ); +PROVIDE ( slc_to_host_chain_recycle = 0x40005f10 ); +PROVIDE ( software_reset = 0x4000264c ); +PROVIDE ( spi_flash_attach = 0x40004644 ); +PROVIDE ( srand = 0x400005f0 ); +PROVIDE ( strcmp = 0x4000bdc8 ); +PROVIDE ( strcpy = 0x4000bec8 ); +PROVIDE ( strlen = 0x4000bf4c ); +PROVIDE ( strncmp = 0x4000bfa8 ); +PROVIDE ( strncpy = 0x4000c0a0 ); +PROVIDE ( strstr = 0x4000e1e0 ); +PROVIDE ( timer_insert = 0x40002c64 ); +PROVIDE ( uartAttach = 0x4000383c ); +PROVIDE ( uart_baudrate_detect = 0x40003924 ); +PROVIDE ( uart_buff_switch = 0x400038a4 ); +PROVIDE ( uart_div_modify = 0x400039d8 ); +PROVIDE ( uart_rx_intr_handler = 0x40003bbc ); +PROVIDE ( uart_rx_one_char = 0x40003b8c ); +PROVIDE ( uart_rx_one_char_block = 0x40003b64 ); +PROVIDE ( uart_rx_readbuff = 0x40003ec8 ); +PROVIDE ( uart_tx_one_char = 0x40003b30 ); +PROVIDE ( wepkey_128 = 0x4000bc40 ); +PROVIDE ( wepkey_64 = 0x4000bb3c ); +PROVIDE ( xthal_bcopy = 0x40000688 ); +PROVIDE ( xthal_copy123 = 0x4000074c ); +PROVIDE ( xthal_get_ccompare = 0x4000dd4c ); +PROVIDE ( xthal_get_ccount = 0x4000dd38 ); +PROVIDE ( xthal_get_interrupt = 0x4000dd58 ); +PROVIDE ( xthal_get_intread = 0x4000dd58 ); +PROVIDE ( xthal_memcpy = 0x400006c4 ); +PROVIDE ( xthal_set_ccompare = 0x4000dd40 ); +PROVIDE ( xthal_set_intclear = 0x4000dd60 ); +PROVIDE ( xthal_spill_registers_into_stack_nw = 0x4000e320 ); +PROVIDE ( xthal_window_spill = 0x4000e324 ); +PROVIDE ( xthal_window_spill_nw = 0x4000e320 ); + +PROVIDE ( Te0 = 0x3fffccf0 ); +PROVIDE ( Td0 = 0x3fffd100 ); +PROVIDE ( Td4s = 0x3fffd500); +PROVIDE ( rcons = 0x3fffd0f0); +PROVIDE ( UartDev = 0x3fffde10 ); +PROVIDE ( flashchip = 0x3fffc714); diff --git a/ports/esp8266/boards/esp8266_1m.ld b/ports/esp8266/boards/esp8266_1m.ld new file mode 100644 index 0000000000..94bc6a229a --- /dev/null +++ b/ports/esp8266/boards/esp8266_1m.ld @@ -0,0 +1,19 @@ +/* GNU linker script for ESP8266 with 1M flash + + Flash layout: + 0x40200000 36k header + iram/dram init + 0x40209000 572k firmware (irom0) + 0x40298000 396k filesystem + 0x402fb000 20k SDK parameters +*/ + +MEMORY +{ + dport0_0_seg : org = 0x3ff00000, len = 16 + dram0_0_seg : org = 0x3ffe8000, len = 80K + iram1_0_seg : org = 0x40100000, len = 32K + irom0_0_seg : org = 0x40209000, len = 572K +} + +/* define common sections and symbols */ +INCLUDE boards/esp8266_common.ld diff --git a/ports/esp8266/boards/esp8266_2m.ld b/ports/esp8266/boards/esp8266_2m.ld new file mode 100644 index 0000000000..b5813ce0fb --- /dev/null +++ b/ports/esp8266/boards/esp8266_2m.ld @@ -0,0 +1,18 @@ +/* GNU linker script for ESP8266 with 2M or more flash + + Flash layout: + 0x40200000 36k header + iram/dram init + 0x40209000 988k firmware (irom0) + 0x40300000 1M+ filesystem (not memory mapped) +*/ + +MEMORY +{ + dport0_0_seg : org = 0x3ff00000, len = 16 + dram0_0_seg : org = 0x3ffe8000, len = 80K + iram1_0_seg : org = 0x40100000, len = 32K + irom0_0_seg : org = 0x40209000, len = 1M - 36K +} + +/* define common sections and symbols */ +INCLUDE boards/esp8266_common.ld diff --git a/ports/esp8266/boards/esp8266_512k.ld b/ports/esp8266/boards/esp8266_512k.ld new file mode 100644 index 0000000000..c4cc798499 --- /dev/null +++ b/ports/esp8266/boards/esp8266_512k.ld @@ -0,0 +1,18 @@ +/* GNU linker script for ESP8266 with 512K flash + + Flash layout: + 0x40200000 36k header + iram/dram init + 0x40209000 456k firmware (irom0) + 0x4027b000 20k SDK parameters +*/ + +MEMORY +{ + dport0_0_seg : org = 0x3ff00000, len = 16 + dram0_0_seg : org = 0x3ffe8000, len = 80K + iram1_0_seg : org = 0x40100000, len = 32K + irom0_0_seg : org = 0x40209000, len = 512K - 36K - 20K +} + +/* define common sections and symbols */ +INCLUDE boards/esp8266_common.ld diff --git a/ports/esp8266/boards/esp8266_common.ld b/ports/esp8266/boards/esp8266_common.ld new file mode 100644 index 0000000000..ae1509faee --- /dev/null +++ b/ports/esp8266/boards/esp8266_common.ld @@ -0,0 +1,319 @@ +/* GNU linker script for ESP8266, common sections and symbols */ + +/* define the top of RAM */ +_heap_end = ORIGIN(dram0_0_seg) + LENGTH(dram0_0_seg); + +PHDRS +{ + dport0_0_phdr PT_LOAD; + dram0_0_phdr PT_LOAD; + dram0_0_bss_phdr PT_LOAD; + iram1_0_phdr PT_LOAD; + irom0_0_phdr PT_LOAD; +} + +ENTRY(firmware_start) +EXTERN(_DebugExceptionVector) +EXTERN(_DoubleExceptionVector) +EXTERN(_KernelExceptionVector) +EXTERN(_NMIExceptionVector) +EXTERN(_UserExceptionVector) + +_firmware_size = ORIGIN(irom0_0_seg) + LENGTH(irom0_0_seg) - 0x40200000; + +PROVIDE(_memmap_vecbase_reset = 0x40000000); + +/* Various memory-map dependent cache attribute settings: */ +_memmap_cacheattr_wb_base = 0x00000110; +_memmap_cacheattr_wt_base = 0x00000110; +_memmap_cacheattr_bp_base = 0x00000220; +_memmap_cacheattr_unused_mask = 0xFFFFF00F; +_memmap_cacheattr_wb_trapnull = 0x2222211F; +_memmap_cacheattr_wba_trapnull = 0x2222211F; +_memmap_cacheattr_wbna_trapnull = 0x2222211F; +_memmap_cacheattr_wt_trapnull = 0x2222211F; +_memmap_cacheattr_bp_trapnull = 0x2222222F; +_memmap_cacheattr_wb_strict = 0xFFFFF11F; +_memmap_cacheattr_wt_strict = 0xFFFFF11F; +_memmap_cacheattr_bp_strict = 0xFFFFF22F; +_memmap_cacheattr_wb_allvalid = 0x22222112; +_memmap_cacheattr_wt_allvalid = 0x22222112; +_memmap_cacheattr_bp_allvalid = 0x22222222; +PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull); + +SECTIONS +{ + + .dport0.rodata : ALIGN(4) + { + _dport0_rodata_start = ABSOLUTE(.); + *(.dport0.rodata) + *(.dport.rodata) + _dport0_rodata_end = ABSOLUTE(.); + } >dport0_0_seg :dport0_0_phdr + + .dport0.literal : ALIGN(4) + { + _dport0_literal_start = ABSOLUTE(.); + *(.dport0.literal) + *(.dport.literal) + _dport0_literal_end = ABSOLUTE(.); + } >dport0_0_seg :dport0_0_phdr + + .dport0.data : ALIGN(4) + { + _dport0_data_start = ABSOLUTE(.); + *(.dport0.data) + *(.dport.data) + _dport0_data_end = ABSOLUTE(.); + } >dport0_0_seg :dport0_0_phdr + + .irom0.text : ALIGN(4) + { + _irom0_text_start = ABSOLUTE(.); + *(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text) + + /* Vendor SDK in v2.1.0-7-gb8fd588 started to build these with + -ffunction-sections -fdata-sections, and require routing to + irom via linker: + https://github.com/espressif/ESP8266_NONOS_SDK/commit/b8fd588a33f0319dc135523b51655e97b483b205 + */ + + *libcrypto.a:(.literal.* .text.*) + *libnet80211.a:(.literal.* .text.*) + *libwpa.a:(.literal.* .text.*) + *libwpa2.a:(.literal.* .text.*) + + /* we put some specific text in this section */ + + *py/argcheck.o*(.literal* .text*) + *py/asm*.o*(.literal* .text*) + *py/bc.o*(.literal* .text*) + *py/binary.o*(.literal* .text*) + *py/builtin*.o*(.literal* .text*) + *py/compile.o*(.literal* .text*) + *py/emit*.o*(.literal* .text*) + *py/persistentcode*.o*(.literal* .text*) + *py/formatfloat.o*(.literal* .text*) + *py/frozenmod.o*(.literal* .text*) + *py/gc.o*(.literal* .text*) + *py/reader*.o*(.literal* .text*) + *py/lexer*.o*(.literal* .text*) + *py/malloc*.o*(.literal* .text*) + *py/map*.o*(.literal* .text*) + *py/mod*.o*(.literal* .text*) + *py/mpprint.o*(.literal* .text*) + *py/mpstate.o*(.literal* .text*) + *py/mpz.o*(.literal* .text*) + *py/native*.o*(.literal* .text*) + *py/nlr*.o*(.literal* .text*) + *py/obj*.o*(.literal* .text*) + *py/opmethods.o*(.literal* .text*) + *py/pairheap*.o*(.literal* .text*) + *py/parse*.o*(.literal* .text*) + *py/profile*.o*(.literal* .text*) + *py/qstr.o*(.literal* .text*) + *py/repl.o*(.literal* .text*) + *py/runtime.o*(.literal* .text*) + *py/scheduler.o*(.literal* .text*) + *py/scope.o*(.literal* .text*) + *py/sequence.o*(.literal* .text*) + *py/showbc.o*(.literal* .text*) + *py/smallint.o*(.literal* .text*) + *py/stackctrl.o*(.literal* .text*) + *py/stream.o*(.literal* .text*) + *py/unicode.o*(.literal* .text*) + *py/vm.o*(.literal* .text*) + *py/vstr.o*(.literal* .text*) + *py/warning.o*(.literal* .text*) + + *extmod/*.o*(.literal* .text*) + + *lib/oofatfs/*.o*(.literal*, .text*) + *lib/axtls/*.o(.literal*, .text*) + *lib/berkeley-db-1.xx/*.o(.literal*, .text*) + *lib/libm/*.o*(.literal*, .text*) + *lib/littlefs/*.o*(.literal*, .text*) + *shared/libc/printf.o*(.literal*, .text*) + *shared/netutils/*.o*(.literal*, .text*) + *shared/readline/*.o(.literal*, .text*) + *shared/runtime/interrupt_char.o*(.literal*, .text*) + *shared/runtime/pyexec.o*(.literal*, .text*) + *shared/runtime/stdout_helpers.o*(.literal*, .text*) + *shared/runtime/sys_stdio_mphal.o*(.literal*, .text*) + *shared/timeutils/*.o*(.literal*, .text*) + *drivers/bus/*.o(.literal* .text*) + + */main.o(.literal* .text*) + *fatfs_port.o(.literal* .text*) + *gccollect.o(.literal* .text*) + *gchelper.o(.literal* .text*) + *help.o(.literal* .text*) + *lexerstr32.o(.literal* .text*) + *utils.o(.literal* .text*) + *modpyb.o(.literal*, .text*) + *machine_pin.o(.literal*, .text*) + *machine_pwm.o(.literal*, .text*) + *machine_rtc.o(.literal*, .text*) + *machine_adc.o(.literal*, .text*) + *machine_uart.o(.literal*, .text*) + *modpybi2c.o(.literal*, .text*) + *modmachine.o(.literal*, .text*) + *machine_wdt.o(.literal*, .text*) + *machine_spi.o(.literal*, .text*) + *machine_hspi.o(.literal*, .text*) + *hspi.o(.literal*, .text*) + *modesp.o(.literal* .text*) + *modnetwork.o(.literal* .text*) + *moduos.o(.literal* .text*) + *modutime.o(.literal* .text*) + *modlwip.o(.literal* .text*) + *modsocket.o(.literal* .text*) + *modonewire.o(.literal* .text*) + *esp_mphal.o(.literal* .text*) + + /* we put as much rodata as possible in this section */ + /* note that only rodata accessed as a machine word is allowed here */ + *py/qstr.o(.rodata.const_pool) + *.o(.rodata.mp_type_*) /* catches type: mp_obj_type_t */ + *.o(.rodata.*_locals_dict*) /* catches types: mp_obj_dict_t, mp_map_elem_t */ + *.o(.rodata.mp_module_*) /* catches types: mp_obj_module_t, mp_obj_dict_t, mp_map_elem_t */ + */frozen.o(.rodata.mp_frozen_sizes) /* frozen modules */ + */frozen.o(.rodata.mp_frozen_content) /* frozen modules */ + + /* for -mforce-l32 */ + */*.o(.rodata*) + + _irom0_text_end = ABSOLUTE(.); + } >irom0_0_seg :irom0_0_phdr + + .text : ALIGN(4) + { + _stext = .; + _text_start = ABSOLUTE(.); + *(.UserEnter.text) + . = ALIGN(16); + *(.DebugExceptionVector.text) + . = ALIGN(16); + *(.NMIExceptionVector.text) + . = ALIGN(16); + *(.KernelExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN(16); + *(.UserExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN(16); + *(.DoubleExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN (16); + *(.entry.text) + *(.init.literal) + *(.init) + *(.literal .text .literal.* .text.* .iram0.literal .iram0.text .iram0.text.*.literal .iram0.text.*) + *(.stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) + *(.fini.literal) + *(.fini) + *(.gnu.version) + _text_end = ABSOLUTE(.); + _etext = .; + } >iram1_0_seg :iram1_0_phdr + + .lit4 : ALIGN(4) + { + _lit4_start = ABSOLUTE(.); + *(*.lit4) + *(.lit4.*) + *(.gnu.linkonce.lit4.*) + _lit4_end = ABSOLUTE(.); + } >iram1_0_seg :iram1_0_phdr + + .data : ALIGN(4) + { + _data_start = ABSOLUTE(.); + *(.data) + *(.data.*) + *(.gnu.linkonce.d.*) + *(.data1) + *(.sdata) + *(.sdata.*) + *(.gnu.linkonce.s.*) + *(.sdata2) + *(.sdata2.*) + *(.gnu.linkonce.s2.*) + *(.jcr) + _data_end = ABSOLUTE(.); + } >dram0_0_seg :dram0_0_phdr + + .rodata : ALIGN(4) + { + _rodata_start = ABSOLUTE(.); + *(.sdk.version) + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r.*) + *(.rodata1) + __XT_EXCEPTION_TABLE__ = ABSOLUTE(.); + *(.xt_except_table) + *(.gcc_except_table) + *(.gnu.linkonce.e.*) + *(.gnu.version_r) + *(.eh_frame) + /* C++ constructor and destructor tables, properly ordered: */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + /* C++ exception handlers table: */ + __XT_EXCEPTION_DESCS__ = ABSOLUTE(.); + *(.xt_except_desc) + *(.gnu.linkonce.h.*) + __XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.); + *(.xt_except_desc_end) + *(.dynamic) + *(.gnu.version_d) + . = ALIGN(4); /* this table MUST be 4-byte aligned */ + _bss_table_start = ABSOLUTE(.); + LONG(_bss_start) + LONG(_bss_end) + _bss_table_end = ABSOLUTE(.); + _rodata_end = ABSOLUTE(.); + } >dram0_0_seg :dram0_0_phdr + + .bss ALIGN(8) (NOLOAD) : ALIGN(4) + { + . = ALIGN (8); + _bss_start = ABSOLUTE(.); + *(.dynsbss) + *(.sbss) + *(.sbss.*) + *(.gnu.linkonce.sb.*) + *(.scommon) + *(.sbss2) + *(.sbss2.*) + *(.gnu.linkonce.sb2.*) + *(.dynbss) + *(.bss) + *(.bss.*) + *(.gnu.linkonce.b.*) + *(COMMON) + . = ALIGN (8); + _bss_end = ABSOLUTE(.); + _heap_start = ABSOLUTE(.); + } >dram0_0_seg :dram0_0_bss_phdr +} + +/* get ROM code address */ +INCLUDE "boards/eagle.rom.addr.v6.ld" diff --git a/ports/esp8266/boards/esp8266_ota.ld b/ports/esp8266/boards/esp8266_ota.ld new file mode 100644 index 0000000000..7fdf6abef0 --- /dev/null +++ b/ports/esp8266/boards/esp8266_ota.ld @@ -0,0 +1,13 @@ +/* GNU linker script for ESP8266 */ + +MEMORY +{ + dport0_0_seg : org = 0x3ff00000, len = 0x10 + dram0_0_seg : org = 0x3ffe8000, len = 0x14000 + iram1_0_seg : org = 0x40100000, len = 0x8000 + /* 0x3c000 is size of bootloader, 0x9000 is size of packed RAM segments */ + irom0_0_seg : org = 0x40200000 + 0x3c000 + 0x9000, len = 0x8f000 +} + +/* define common sections and symbols */ +INCLUDE boards/esp8266_common.ld diff --git a/ports/esp8266/boards/manifest.py b/ports/esp8266/boards/manifest.py new file mode 100644 index 0000000000..598572d62a --- /dev/null +++ b/ports/esp8266/boards/manifest.py @@ -0,0 +1,6 @@ +freeze("$(PORT_DIR)/modules") +freeze("$(MPY_DIR)/tools", ("upip.py", "upip_utarfile.py")) +freeze("$(MPY_DIR)/drivers/dht", "dht.py") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/webrepl/manifest.py") +include("$(MPY_DIR)/drivers/neopixel/manifest.py") diff --git a/ports/esp8266/esp_init_data.c b/ports/esp8266/esp_init_data.c new file mode 100644 index 0000000000..eb4d99ebda --- /dev/null +++ b/ports/esp8266/esp_init_data.c @@ -0,0 +1,77 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "ets_sys.h" +#include "etshal.h" +#include "esp_mphal.h" +#include "user_interface.h" +#include "extmod/misc.h" + +NORETURN void call_user_start(void); +void ets_printf(const char *fmt, ...); +extern char flashchip; + +static const uint8_t default_init_data[] __attribute__((aligned(4))) = { + 0x05, 0x00, 0x04, 0x02, 0x05, 0x05, 0x05, 0x02, 0x05, 0x00, 0x04, 0x05, 0x05, 0x04, 0x05, 0x05, + 0x04, 0xfe, 0xfd, 0xff, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 0xe1, 0x0a, 0xff, 0xff, 0xf8, 0x00, + 0xf8, 0xf8, 0x52, 0x4e, 0x4a, 0x44, 0x40, 0x38, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe1, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x93, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +void firmware_start(void) { + // For SDK 1.5.2, either address has shifted and not mirrored in + // eagle.rom.addr.v6.ld, or extra initial member was added. + SpiFlashChip *flash = (SpiFlashChip *)(&flashchip + 4); + + char buf[128]; + SPIRead(flash->chip_size - 4 * 0x1000, buf, sizeof(buf)); + /*for (int i = 0; i < sizeof(buf); i++) { + static char hexf[] = "%x "; + ets_printf(hexf, buf[i]); + }*/ + + bool inited = false; + for (int i = 0; i < sizeof(buf); i++) { + if (buf[i] != 0xff) { + inited = true; + break; + } + } + + if (!inited) { + static char msg[] = "Writing init data\n"; + ets_printf(msg); + SPIRead((uint32_t)&default_init_data - 0x40200000, buf, sizeof(buf)); + SPIWrite(flash->chip_size - 4 * 0x1000, buf, sizeof(buf)); + } + + asm ("j call_user_start"); +} diff --git a/ports/esp8266/esp_mphal.c b/ports/esp8266/esp_mphal.c new file mode 100644 index 0000000000..3cb4807333 --- /dev/null +++ b/ports/esp8266/esp_mphal.c @@ -0,0 +1,186 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "ets_sys.h" +#include "etshal.h" +#include "uart.h" +#include "esp_mphal.h" +#include "user_interface.h" +#include "ets_alt_task.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/misc.h" +#include "shared/runtime/pyexec.h" + +STATIC byte stdin_ringbuf_array[256]; +ringbuf_t stdin_ringbuf = {stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, 0}; +void mp_hal_debug_tx_strn_cooked(void *env, const char *str, uint32_t len); +const mp_print_t mp_debug_print = {NULL, mp_hal_debug_tx_strn_cooked}; + +int uart_attached_to_dupterm; + +void mp_hal_init(void) { + // ets_wdt_disable(); // it's a pain while developing + mp_hal_rtc_init(); + uart_init(UART_BIT_RATE_115200, UART_BIT_RATE_115200); + uart_attached_to_dupterm = 0; +} + +void MP_FASTCODE(mp_hal_delay_us)(uint32_t us) { + uint32_t start = system_get_time(); + while (system_get_time() - start < us) { + ets_event_poll(); + } +} + +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + if ((poll_flags & MP_STREAM_POLL_RD) && stdin_ringbuf.iget != stdin_ringbuf.iput) { + ret |= MP_STREAM_POLL_RD; + } + return ret; +} + +int mp_hal_stdin_rx_chr(void) { + for (;;) { + int c = ringbuf_get(&stdin_ringbuf); + if (c != -1) { + return c; + } + #if 0 + // Idles CPU but need more testing before enabling + if (!ets_loop_iter()) { + asm ("waiti 0"); + } + #else + mp_hal_delay_us(1); + #endif + } +} + +#if 0 +void mp_hal_debug_str(const char *str) { + while (*str) { + uart_tx_one_char(UART0, *str++); + } + uart_flush(UART0); +} +#endif + +void mp_hal_stdout_tx_strn(const char *str, uint32_t len) { + mp_uos_dupterm_tx_strn(str, len); +} + +void mp_hal_debug_tx_strn_cooked(void *env, const char *str, uint32_t len) { + (void)env; + while (len--) { + if (*str == '\n') { + uart_tx_one_char(UART0, '\r'); + } + uart_tx_one_char(UART0, *str++); + } +} + +uint32_t MP_FASTCODE(mp_hal_ticks_ms)(void) { + // Compute milliseconds from 64-bit microsecond counter + system_time_update(); + return ((uint64_t)system_time_high_word << 32 | (uint64_t)system_time_low_word) / 1000; +} + +void MP_FASTCODE(mp_hal_delay_ms)(uint32_t delay) { + mp_hal_delay_us(delay * 1000); +} + +uint64_t mp_hal_time_ns(void) { + return pyb_rtc_get_us_since_epoch() * 1000ULL; +} + +void ets_event_poll(void) { + ets_loop_iter(); + mp_handle_pending(true); +} + +void __assert_func(const char *file, int line, const char *func, const char *expr) { + printf("assert:%s:%d:%s: %s\n", file, line, func, expr); + mp_raise_msg(&mp_type_AssertionError, MP_ERROR_TEXT("C-level assert")); +} + +// May be called by uart0_rx_intr_handler. +void MP_FASTCODE(mp_hal_signal_input)(void) { + #if MICROPY_REPL_EVENT_DRIVEN + system_os_post(UART_TASK_ID, 0, 0); + #endif +} + +STATIC void dupterm_task_handler(os_event_t *evt) { + static byte lock; + if (lock) { + return; + } + lock = 1; + while (1) { + int c = mp_uos_dupterm_rx_chr(); + if (c < 0) { + break; + } + ringbuf_put(&stdin_ringbuf, c); + } + mp_hal_signal_input(); + lock = 0; +} + +STATIC os_event_t dupterm_evt_queue[4]; + +void dupterm_task_init() { + system_os_task(dupterm_task_handler, DUPTERM_TASK_ID, dupterm_evt_queue, MP_ARRAY_SIZE(dupterm_evt_queue)); +} + +void mp_hal_signal_dupterm_input(void) { + system_os_post(DUPTERM_TASK_ID, 0, 0); +} + +// Get pointer to esf_buf bookkeeping structure +void *ets_get_esf_buf_ctlblk(void) { + // Get literal ptr before start of esf_rx_buf_alloc func + extern void *esf_rx_buf_alloc(); + return ((void **)esf_rx_buf_alloc)[-1]; +} + +// Get number of esf_buf free buffers of given type, as encoded by index +// idx 0 corresponds to buf types 1, 2; 1 - 4; 2 - 5; 3 - 7; 4 - 8 +// Only following buf types appear to be used: +// 1 - tx buffer, 5 - management frame tx buffer; 8 - rx buffer +int ets_esf_free_bufs(int idx) { + uint32_t *p = ets_get_esf_buf_ctlblk(); + uint32_t *b = (uint32_t *)p[idx]; + int cnt = 0; + while (b) { + b = (uint32_t *)b[0x20 / 4]; + cnt++; + } + return cnt; +} diff --git a/ports/esp8266/esp_mphal.h b/ports/esp8266/esp_mphal.h new file mode 100644 index 0000000000..b0351877e8 --- /dev/null +++ b/ports/esp8266/esp_mphal.h @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "user_interface.h" +#include "py/ringbuf.h" +#include "shared/runtime/interrupt_char.h" +#include "xtirq.h" + +void mp_sched_keyboard_interrupt(void); + +struct _mp_print_t; +// Structure for UART-only output via mp_printf() +extern const struct _mp_print_t mp_debug_print; + +extern ringbuf_t stdin_ringbuf; +// Call this after putting data to stdin_ringbuf +void mp_hal_signal_input(void); +// Call this when data is available in dupterm object +void mp_hal_signal_dupterm_input(void); + +// This variable counts how many times the UART is attached to dupterm +extern int uart_attached_to_dupterm; + +void mp_hal_init(void); +void mp_hal_rtc_init(void); + +__attribute__((always_inline)) static inline uint32_t mp_hal_ticks_us(void) { + return system_get_time(); +} + +__attribute__((always_inline)) static inline uint32_t mp_hal_ticks_cpu(void) { + uint32_t ccount; + __asm__ __volatile__ ("rsr %0,ccount" : "=a" (ccount)); + return ccount; +} + +void mp_hal_delay_us(uint32_t); +void mp_hal_set_interrupt_char(int c); +uint32_t mp_hal_get_cpu_freq(void); + +#define UART_TASK_ID 0 +#define DUPTERM_TASK_ID 1 +void uart_task_init(); +void dupterm_task_init(); + +void ets_event_poll(void); +#define ETS_POLL_WHILE(cond) { while (cond) ets_event_poll(); } + +// needed for machine.I2C +#include "osapi.h" +#define mp_hal_delay_us_fast(us) os_delay_us(us) + +#define mp_hal_quiet_timing_enter() disable_irq() +#define mp_hal_quiet_timing_exit(irq_state) enable_irq(irq_state) + +// C-level pin HAL +#include "etshal.h" +#include "gpio.h" +#include "modmachine.h" +#define MP_HAL_PIN_FMT "%u" +#define mp_hal_pin_obj_t uint32_t +#define mp_hal_get_pin_obj(o) mp_obj_get_pin(o) +#define mp_hal_pin_name(p) (p) +void mp_hal_pin_input(mp_hal_pin_obj_t pin); +void mp_hal_pin_output(mp_hal_pin_obj_t pin); +void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin); +#define mp_hal_pin_od_low(p) do { \ + if ((p) == 16) { WRITE_PERI_REG(RTC_GPIO_ENABLE, (READ_PERI_REG(RTC_GPIO_ENABLE) & ~1) | 1); } \ + else { gpio_output_set(0, 1 << (p), 1 << (p), 0); } \ +} while (0) +#define mp_hal_pin_od_high(p) do { \ + if ((p) == 16) { WRITE_PERI_REG(RTC_GPIO_ENABLE, (READ_PERI_REG(RTC_GPIO_ENABLE) & ~1)); } \ + else { gpio_output_set(0, 0, 0, 1 << (p)); /* set as input to avoid glitches */ } \ +} while (0) +// The DHT driver requires using the open-drain feature of the GPIO to get it to work reliably +#define mp_hal_pin_od_high_dht(p) do { \ + if ((p) == 16) { WRITE_PERI_REG(RTC_GPIO_ENABLE, (READ_PERI_REG(RTC_GPIO_ENABLE) & ~1)); } \ + else { gpio_output_set(1 << (p), 0, 1 << (p), 0); } \ +} while (0) +#define mp_hal_pin_read(p) pin_get(p) +#define mp_hal_pin_write(p, v) pin_set((p), (v)) + +void *ets_get_esf_buf_ctlblk(void); +int ets_esf_free_bufs(int idx); diff --git a/ports/esp8266/espapa102.c b/ports/esp8266/espapa102.c new file mode 100644 index 0000000000..2bea26609a --- /dev/null +++ b/ports/esp8266/espapa102.c @@ -0,0 +1,115 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Robert Foss, Daniel Busch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mpconfig.h" +#if MICROPY_ESP8266_APA102 + +#include +#include "c_types.h" +#include "eagle_soc.h" +#include "user_interface.h" +#include "espapa102.h" + +#define NOP asm volatile (" nop \n\t") + +static inline void _esp_apa102_send_byte(uint32_t clockPinMask, uint32_t dataPinMask, uint8_t byte) { + for (uint32_t i = 0; i < 8; i++) { + if (byte & 0x80) { + // set data pin high + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, dataPinMask); + } else { + // set data pin low + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, dataPinMask); + } + + // set clock pin high + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, clockPinMask); + byte <<= 1; + NOP; + NOP; + + // set clock pin low + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, clockPinMask); + NOP; + NOP; + } +} + +static inline void _esp_apa102_send_colors(uint32_t clockPinMask, uint32_t dataPinMask, uint8_t *pixels, uint32_t numBytes) { + for (uint32_t i = 0; i < numBytes / 4; i++) { + _esp_apa102_send_byte(clockPinMask, dataPinMask, pixels[i * 4 + 3] | 0xE0); + _esp_apa102_send_byte(clockPinMask, dataPinMask, pixels[i * 4 + 2]); + _esp_apa102_send_byte(clockPinMask, dataPinMask, pixels[i * 4 + 1]); + _esp_apa102_send_byte(clockPinMask, dataPinMask, pixels[i * 4]); + } +} + +static inline void _esp_apa102_start_frame(uint32_t clockPinMask, uint32_t dataPinMask) { + for (uint32_t i = 0; i < 4; i++) { + _esp_apa102_send_byte(clockPinMask, dataPinMask, 0x00); + } +} + +static inline void _esp_apa102_append_additionial_cycles(uint32_t clockPinMask, uint32_t dataPinMask, uint32_t numBytes) { + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, dataPinMask); + + // we need to write some more clock cycles, because each led + // delays the data by one edge after inverting the clock + for (uint32_t i = 0; i < numBytes / 8 + ((numBytes / 4) % 2); i++) { + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, clockPinMask); + NOP; + NOP; + + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, clockPinMask); + NOP; + NOP; + } +} + +static inline void _esp_apa102_end_frame(uint32_t clockPinMask, uint32_t dataPinMask) { + for (uint32_t i = 0; i < 4; i++) { + _esp_apa102_send_byte(clockPinMask, dataPinMask, 0xFF); + } +} + +void esp_apa102_write(uint8_t clockPin, uint8_t dataPin, uint8_t *pixels, uint32_t numBytes) { + uint32_t clockPinMask, dataPinMask; + + clockPinMask = 1 << clockPin; + dataPinMask = 1 << dataPin; + + // start the frame + _esp_apa102_start_frame(clockPinMask, dataPinMask); + + // write pixels + _esp_apa102_send_colors(clockPinMask, dataPinMask, pixels, numBytes); + + // end the frame + _esp_apa102_append_additionial_cycles(clockPinMask, dataPinMask, numBytes); + _esp_apa102_end_frame(clockPinMask, dataPinMask); +} + +#endif diff --git a/ports/esp8266/espapa102.h b/ports/esp8266/espapa102.h new file mode 100644 index 0000000000..dd7c5ab729 --- /dev/null +++ b/ports/esp8266/espapa102.h @@ -0,0 +1,31 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Robert Foss, Daniel Busch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP8266_ESPAPA102_H +#define MICROPY_INCLUDED_ESP8266_ESPAPA102_H + +void esp_apa102_write(uint8_t clockPin, uint8_t dataPin, uint8_t *pixels, uint32_t numBytes); + +#endif // MICROPY_INCLUDED_ESP8266_ESPAPA102_H diff --git a/ports/esp8266/esppwm.c b/ports/esp8266/esppwm.c new file mode 100644 index 0000000000..d5bcea9acd --- /dev/null +++ b/ports/esp8266/esppwm.c @@ -0,0 +1,426 @@ +/****************************************************************************** + * Copyright 2013-2014 Espressif Systems (Wuxi) + * + * FileName: pwm.c + * + * Description: pwm driver + * + * Modification history: + * 2014/5/1, v1.0 create this file. + * 2016/3/2: Modifications by dpgeorge to suit MicroPython +*******************************************************************************/ +#include +#include + +#include "etshal.h" +#include "os_type.h" +#include "gpio.h" + +#include "esppwm.h" + +#include "py/mpprint.h" +#define PWM_DBG(...) +// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__) + +#define ICACHE_RAM_ATTR // __attribute__((section(".text"))) + +#define PWM_CHANNEL 8 +#define PWM_DEPTH 1023 +#define PWM_FREQ_MAX 1000 +#define PWM_1S 1000000 + +struct pwm_single_param { + uint16_t gpio_set; + uint16_t gpio_clear; + uint32_t h_time; +}; + +struct pwm_param { + uint32_t period; + uint16_t freq; + uint16_t duty[PWM_CHANNEL]; +}; + +STATIC const uint8_t pin_num[PWM_CHANNEL] = {0, 2, 4, 5, 12, 13, 14, 15}; + +STATIC struct pwm_single_param pwm_single_toggle[2][PWM_CHANNEL + 1]; +STATIC struct pwm_single_param *pwm_single; + +STATIC struct pwm_param pwm; + +STATIC int8_t pwm_out_io_num[PWM_CHANNEL] = {-1, -1, -1, -1, -1, -1, -1, -1}; + +STATIC uint8_t pwm_channel_toggle[2]; +STATIC uint8_t *pwm_channel; +STATIC uint8_t pwm_toggle = 1; +STATIC uint8_t pwm_timer_down = 1; +STATIC uint8_t pwm_current_channel = 0; +STATIC uint16_t pwm_gpio = 0; +STATIC uint8_t pwm_channel_num = 0; +STATIC volatile uint8_t pwm_toggle_request = 0; + +// XXX: 0xffffffff/(80000000/16)=35A +#define US_TO_RTC_TIMER_TICKS(t) \ + ((t) ? \ + (((t) > 0x35A) ? \ + (((t) >> 2) * ((APB_CLK_FREQ >> 4) / 250000) + ((t) & 0x3) * ((APB_CLK_FREQ >> 4) / 1000000)) : \ + (((t) * (APB_CLK_FREQ >> 4)) / 1000000)) : \ + 0) + +// FRC1 +#define FRC1_ENABLE_TIMER BIT7 + +typedef enum { + DIVDED_BY_1 = 0, + DIVDED_BY_16 = 4, + DIVDED_BY_256 = 8, +} TIMER_PREDIVED_MODE; + +typedef enum { + TM_LEVEL_INT = 1, + TM_EDGE_INT = 0, +} TIMER_INT_MODE; + +STATIC void ICACHE_FLASH_ATTR +pwm_insert_sort(struct pwm_single_param pwm[], uint8 n) { + uint8 i; + + for (i = 1; i < n; i++) { + if (pwm[i].h_time < pwm[i - 1].h_time) { + int8 j = i - 1; + struct pwm_single_param tmp; + + memcpy(&tmp, &pwm[i], sizeof(struct pwm_single_param)); + memcpy(&pwm[i], &pwm[i - 1], sizeof(struct pwm_single_param)); + + while (tmp.h_time < pwm[j].h_time) { + memcpy(&pwm[j + 1], &pwm[j], sizeof(struct pwm_single_param)); + j--; + if (j < 0) { + break; + } + } + + memcpy(&pwm[j + 1], &tmp, sizeof(struct pwm_single_param)); + } + } +} + +STATIC volatile uint8 critical = 0; + +#define LOCK_PWM(c) do { \ + while ((c) == 1); \ + (c) = 1; \ +} while (0) + +#define UNLOCK_PWM(c) do { \ + (c) = 0; \ +} while (0) + +void ICACHE_FLASH_ATTR +pwm_start(void) { + uint8 i, j; + PWM_DBG("--Function pwm_start() is called\n"); + PWM_DBG("pwm_gpio:%x,pwm_channel_num:%d\n",pwm_gpio,pwm_channel_num); + PWM_DBG("pwm_out_io_num[0]:%d,[1]:%d,[2]:%d\n",pwm_out_io_num[0],pwm_out_io_num[1],pwm_out_io_num[2]); + PWM_DBG("pwm.period:%d,pwm.duty[0]:%d,[1]:%d,[2]:%d\n",pwm.period,pwm.duty[0],pwm.duty[1],pwm.duty[2]); + + LOCK_PWM(critical); // enter critical + + // if a toggle is pending, we reset it since we're changing the settings again + pwm_toggle_request = 0; + + struct pwm_single_param *local_single = pwm_single_toggle[pwm_toggle ^ 0x01]; + uint8 *local_channel = &pwm_channel_toggle[pwm_toggle ^ 0x01]; + + // step 1: init PWM_CHANNEL+1 channels param + for (i = 0; i < pwm_channel_num; i++) { + uint32 us = pwm.period * pwm.duty[i] / PWM_DEPTH; + local_single[i].h_time = US_TO_RTC_TIMER_TICKS(us); + PWM_DBG("i:%d us:%d ht:%d\n",i,us,local_single[i].h_time); + local_single[i].gpio_set = 0; + local_single[i].gpio_clear = 1 << pin_num[pwm_out_io_num[i]]; + } + + local_single[pwm_channel_num].h_time = US_TO_RTC_TIMER_TICKS(pwm.period); + local_single[pwm_channel_num].gpio_set = pwm_gpio; + local_single[pwm_channel_num].gpio_clear = 0; + PWM_DBG("i:%d period:%d ht:%d\n",pwm_channel_num,pwm.period,local_single[pwm_channel_num].h_time); + // step 2: sort, small to big + pwm_insert_sort(local_single, pwm_channel_num + 1); + + *local_channel = pwm_channel_num + 1; + PWM_DBG("1channel:%d,single[0]:%d,[1]:%d,[2]:%d,[3]:%d\n",*local_channel,local_single[0].h_time,local_single[1].h_time,local_single[2].h_time,local_single[3].h_time); + // step 3: combine same duty channels + for (i = pwm_channel_num; i > 0; i--) { + if (local_single[i].h_time == local_single[i - 1].h_time) { + local_single[i - 1].gpio_set |= local_single[i].gpio_set; + local_single[i - 1].gpio_clear |= local_single[i].gpio_clear; + + for (j = i + 1; j < *local_channel; j++) { + memcpy(&local_single[j - 1], &local_single[j], sizeof(struct pwm_single_param)); + } + + (*local_channel)--; + } + } + PWM_DBG("2channel:%d,single[0]:%d,[1]:%d,[2]:%d,[3]:%d\n",*local_channel,local_single[0].h_time,local_single[1].h_time,local_single[2].h_time,local_single[3].h_time); + // step 4: cacl delt time + for (i = *local_channel - 1; i > 0; i--) { + local_single[i].h_time -= local_single[i - 1].h_time; + } + + // step 5: last channel needs to clean + local_single[*local_channel - 1].gpio_clear = 0; + + // step 6: if first channel duty is 0, remove it + if (local_single[0].h_time == 0) { + local_single[*local_channel - 1].gpio_set &= ~local_single[0].gpio_clear; + local_single[*local_channel - 1].gpio_clear |= local_single[0].gpio_clear; + + for (i = 1; i < *local_channel; i++) { + memcpy(&local_single[i - 1], &local_single[i], sizeof(struct pwm_single_param)); + } + + (*local_channel)--; + } + + // if timer is down, need to set gpio and start timer + if (pwm_timer_down == 1) { + pwm_channel = local_channel; + pwm_single = local_single; + // start + gpio_output_set(local_single[0].gpio_set, local_single[0].gpio_clear, pwm_gpio, 0); + + // do the first toggle because timer has to have a valid set to do it's job + pwm_toggle ^= 0x01; + + pwm_timer_down = 0; + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, local_single[0].h_time); + } else { + // request pwm_tim1_intr_handler to swap the timing buffers + pwm_toggle_request = 1; + } + + UNLOCK_PWM(critical); // leave critical + PWM_DBG("3channel:%d,single[0]:%d,[1]:%d,[2]:%d,[3]:%d\n",*local_channel,local_single[0].h_time,local_single[1].h_time,local_single[2].h_time,local_single[3].h_time); +} + +/****************************************************************************** + * FunctionName : pwm_set_duty + * Description : set each channel's duty params + * Parameters : int16_t duty : 0 ~ PWM_DEPTH + * uint8 channel : channel index + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +pwm_set_duty(int16_t duty, uint8 channel) { + uint8 i; + for (i = 0; i < pwm_channel_num; i++) { + if (pwm_out_io_num[i] == channel) { + channel = i; + break; + } + } + if (i == pwm_channel_num) { // non found + return; + } + + LOCK_PWM(critical); // enter critical + if (duty < 1) { + pwm.duty[channel] = 0; + } else if (duty >= PWM_DEPTH) { + pwm.duty[channel] = PWM_DEPTH; + } else { + pwm.duty[channel] = duty; + } + UNLOCK_PWM(critical); // leave critical +} + +/****************************************************************************** + * FunctionName : pwm_set_freq + * Description : set pwm frequency + * Parameters : uint16 freq : 100hz typically + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +pwm_set_freq(uint16 freq, uint8 channel) { + LOCK_PWM(critical); // enter critical + if (freq > PWM_FREQ_MAX) { + pwm.freq = PWM_FREQ_MAX; + } else if (freq < 1) { + pwm.freq = 1; + } else { + pwm.freq = freq; + } + + pwm.period = PWM_1S / pwm.freq; + UNLOCK_PWM(critical); // leave critical +} + +/****************************************************************************** + * FunctionName : pwm_get_duty + * Description : get duty of each channel + * Parameters : uint8 channel : channel index + * Returns : NONE +*******************************************************************************/ +uint16 ICACHE_FLASH_ATTR +pwm_get_duty(uint8 channel) { + uint8 i; + for (i = 0; i < pwm_channel_num; i++) { + if (pwm_out_io_num[i] == channel) { + channel = i; + break; + } + } + if (i == pwm_channel_num) { // non found + return 0; + } + + return pwm.duty[channel]; +} + +/****************************************************************************** + * FunctionName : pwm_get_freq + * Description : get pwm frequency + * Parameters : NONE + * Returns : uint16 : pwm frequency +*******************************************************************************/ +uint16 ICACHE_FLASH_ATTR +pwm_get_freq(uint8 channel) { + return pwm.freq; +} + +/****************************************************************************** + * FunctionName : pwm_period_timer + * Description : pwm period timer function, output high level, + * start each channel's high level timer + * Parameters : NONE + * Returns : NONE +*******************************************************************************/ +STATIC void ICACHE_RAM_ATTR +pwm_tim1_intr_handler(void *dummy) { + (void)dummy; + + RTC_CLR_REG_MASK(FRC1_INT_ADDRESS, FRC1_INT_CLR_MASK); + + if (pwm_current_channel >= (*pwm_channel - 1)) { // *pwm_channel may change outside + + if (pwm_toggle_request != 0) { + pwm_toggle ^= 1; + pwm_toggle_request = 0; + } + + pwm_single = pwm_single_toggle[pwm_toggle]; + pwm_channel = &pwm_channel_toggle[pwm_toggle]; + + gpio_output_set(pwm_single[*pwm_channel - 1].gpio_set, + pwm_single[*pwm_channel - 1].gpio_clear, + pwm_gpio, + 0); + + pwm_current_channel = 0; + + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, pwm_single[pwm_current_channel].h_time); + } else { + gpio_output_set(pwm_single[pwm_current_channel].gpio_set, + pwm_single[pwm_current_channel].gpio_clear, + pwm_gpio, 0); + + pwm_current_channel++; + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, pwm_single[pwm_current_channel].h_time); + } +} + +/****************************************************************************** + * FunctionName : pwm_init + * Description : pwm gpio, params and timer initialization + * Parameters : uint16 freq : pwm freq param + * uint16 *duty : each channel's duty + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +pwm_init(void) { + uint8 i; + + RTC_REG_WRITE(FRC1_CTRL_ADDRESS, // FRC2_AUTO_RELOAD| + DIVDED_BY_16 + | FRC1_ENABLE_TIMER + | TM_EDGE_INT); + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0); + + for (i = 0; i < PWM_CHANNEL; i++) { + pwm_gpio = 0; + pwm.duty[i] = 0; + } + + pwm_set_freq(500, 0); + pwm_start(); + + ETS_FRC_TIMER1_INTR_ATTACH(pwm_tim1_intr_handler, NULL); + TM1_EDGE_INT_ENABLE(); + ETS_FRC1_INTR_ENABLE(); +} + +int ICACHE_FLASH_ATTR +pwm_add(uint8_t pin_id, uint32_t pin_mux, uint32_t pin_func) { + PWM_DBG("--Function pwm_add() is called. channel:%d\n", channel); + PWM_DBG("pwm_gpio:%x,pwm_channel_num:%d\n",pwm_gpio,pwm_channel_num); + PWM_DBG("pwm_out_io_num[0]:%d,[1]:%d,[2]:%d\n",pwm_out_io_num[0],pwm_out_io_num[1],pwm_out_io_num[2]); + PWM_DBG("pwm.duty[0]:%d,[1]:%d,[2]:%d\n",pwm.duty[0],pwm.duty[1],pwm.duty[2]); + int channel = -1; + for (int i = 0; i < PWM_CHANNEL; ++i) { + if (pin_num[i] == pin_id) { + channel = i; + break; + } + } + if (channel == -1) { + return -1; + } + uint8 i; + for (i = 0; i < PWM_CHANNEL; i++) { + if (pwm_out_io_num[i] == channel) { // already exist + return channel; + } + if (pwm_out_io_num[i] == -1) { // empty exist + LOCK_PWM(critical); // enter critical + pwm_out_io_num[i] = channel; + pwm.duty[i] = 0; + pwm_gpio |= (1 << pin_num[channel]); + PIN_FUNC_SELECT(pin_mux, pin_func); + GPIO_REG_WRITE(GPIO_PIN_ADDR(GPIO_ID_PIN(pin_num[channel])), GPIO_REG_READ(GPIO_PIN_ADDR(GPIO_ID_PIN(pin_num[channel]))) & (~GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_ENABLE))); // disable open drain; + pwm_channel_num++; + UNLOCK_PWM(critical); // leave critical + return channel; + } + } + return -1; +} + +bool ICACHE_FLASH_ATTR +pwm_delete(uint8 channel) { + PWM_DBG("--Function pwm_delete() is called. channel:%d\n", channel); + PWM_DBG("pwm_gpio:%x,pwm_channel_num:%d\n",pwm_gpio,pwm_channel_num); + PWM_DBG("pwm_out_io_num[0]:%d,[1]:%d,[2]:%d\n",pwm_out_io_num[0],pwm_out_io_num[1],pwm_out_io_num[2]); + PWM_DBG("pwm.duty[0]:%d,[1]:%d,[2]:%d\n",pwm.duty[0],pwm.duty[1],pwm.duty[2]); + uint8 i,j; + for (i = 0; i < pwm_channel_num; i++) { + if (pwm_out_io_num[i] == channel) { // exist + LOCK_PWM(critical); // enter critical + pwm_out_io_num[i] = -1; + pwm_gpio &= ~(1 << pin_num[channel]); // clear the bit + for (j = i; j < pwm_channel_num - 1; j++) { + pwm_out_io_num[j] = pwm_out_io_num[j + 1]; + pwm.duty[j] = pwm.duty[j + 1]; + } + pwm_out_io_num[pwm_channel_num - 1] = -1; + pwm.duty[pwm_channel_num - 1] = 0; + pwm_channel_num--; + UNLOCK_PWM(critical); // leave critical + return true; + } + } + // non found + return true; +} diff --git a/ports/esp8266/esppwm.h b/ports/esp8266/esppwm.h new file mode 100644 index 0000000000..288ef6d538 --- /dev/null +++ b/ports/esp8266/esppwm.h @@ -0,0 +1,17 @@ +#ifndef MICROPY_INCLUDED_ESP8266_ESPPWM_H +#define MICROPY_INCLUDED_ESP8266_ESPPWM_H + +#include +#include + +void pwm_init(void); +void pwm_start(void); + +void pwm_set_duty(int16_t duty, uint8_t channel); +uint16_t pwm_get_duty(uint8_t channel); +void pwm_set_freq(uint16_t freq, uint8_t channel); +uint16_t pwm_get_freq(uint8_t channel); +int pwm_add(uint8_t pin_id, uint32_t pin_mux, uint32_t pin_func); +bool pwm_delete(uint8_t channel); + +#endif // MICROPY_INCLUDED_ESP8266_ESPPWM_H diff --git a/ports/esp8266/ets_alt_task.c b/ports/esp8266/ets_alt_task.c new file mode 100644 index 0000000000..71d022a32e --- /dev/null +++ b/ports/esp8266/ets_alt_task.c @@ -0,0 +1,240 @@ +#include +#include "osapi.h" +#include "os_type.h" +#include "ets_sys.h" +#include +#include "etshal.h" +#include "user_interface.h" +#include "ets_alt_task.h" + +// Use standard ets_task or alternative impl +#define USE_ETS_TASK 0 + +#define MP_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +struct task_entry { + os_event_t *queue; + os_task_t task; + uint8_t qlen; + uint8_t prio; + int8_t i_get; + int8_t i_put; +}; + +static void (*idle_cb)(void *); +static void *idle_arg; + +#if ESP_SDK_VERSION >= 010500 +#define FIRST_PRIO 0 +#else +#define FIRST_PRIO 0x14 +#endif +#define LAST_PRIO 0x20 +#define PRIO2ID(prio) ((prio) - FIRST_PRIO) + +volatile struct task_entry emu_tasks[PRIO2ID(LAST_PRIO) + 1]; + +static inline int prio2id(uint8_t prio) { + int id = PRIO2ID(prio); + if (id < 0 || id >= MP_ARRAY_SIZE(emu_tasks)) { + printf("task prio out of range: %d\n", prio); + while (1) { + ; + } + } + return id; +} + +#if DEBUG +void dump_task(int prio, volatile struct task_entry *t) { + printf("q for task %d: queue: %p, get ptr: %d, put ptr: %d, qlen: %d\n", + prio, t->queue, t->i_get, t->i_put, t->qlen); +} + +void dump_tasks(void) { + for (int i = 0; i < MP_ARRAY_SIZE(emu_tasks); i++) { + if (emu_tasks[i].qlen) { + dump_task(i + FIRST_PRIO, &emu_tasks[i]); + } + } + printf("====\n"); +} +#endif + +bool ets_task(os_task_t task, uint8 prio, os_event_t *queue, uint8 qlen) { + static unsigned cnt; + printf("#%d ets_task(%p, %d, %p, %d)\n", cnt++, task, prio, queue, qlen); + #if USE_ETS_TASK + return _ets_task(task, prio, queue, qlen); + #else + int id = prio2id(prio); + emu_tasks[id].task = task; + emu_tasks[id].queue = queue; + emu_tasks[id].qlen = qlen; + emu_tasks[id].i_get = 0; + emu_tasks[id].i_put = 0; + return true; + #endif +} + +bool ets_post(uint8 prio, os_signal_t sig, os_param_t param) { +// static unsigned cnt; printf("#%d ets_post(%d, %x, %x)\n", cnt++, prio, sig, param); + #if USE_ETS_TASK + return _ets_post(prio, sig, param); + #else + ets_intr_lock(); + + const int id = prio2id(prio); + os_event_t *q = emu_tasks[id].queue; + if (emu_tasks[id].i_put == -1) { + // queue is full + printf("ets_post: task %d queue full\n", prio); + return 1; + } + q = &q[emu_tasks[id].i_put++]; + q->sig = sig; + q->par = param; + if (emu_tasks[id].i_put == emu_tasks[id].qlen) { + emu_tasks[id].i_put = 0; + } + if (emu_tasks[id].i_put == emu_tasks[id].i_get) { + // queue got full + emu_tasks[id].i_put = -1; + } + // printf("after ets_post: "); dump_task(prio, &emu_tasks[id]); + // dump_tasks(); + + ets_intr_unlock(); + + return 0; + #endif +} + +int ets_loop_iter_disable = 0; +int ets_loop_dont_feed_sw_wdt = 0; + +// to implement a 64-bit wide microsecond counter +uint32_t system_time_low_word = 0; +uint32_t system_time_high_word = 0; + +void system_time_update(void) { + // Handle overflow of system microsecond counter + ets_intr_lock(); + uint32_t system_time_cur = system_get_time(); + if (system_time_cur < system_time_low_word) { + system_time_high_word += 1; // record overflow of low 32-bits + } + system_time_low_word = system_time_cur; + ets_intr_unlock(); +} + +bool ets_loop_iter(void) { + if (ets_loop_iter_disable) { + return false; + } + + // Update 64-bit microsecond counter + system_time_update(); + + // 6 words before pend_flag_noise_check is a variable that is used by + // the software WDT. A 1.6 second period timer will increment this + // variable and if it gets to 2 then the SW WDT will trigger a reset. + extern uint32_t pend_flag_noise_check; + uint32_t *sw_wdt = &pend_flag_noise_check - 6; + + // static unsigned cnt; + bool progress = false; + for (volatile struct task_entry *t = emu_tasks; t < &emu_tasks[MP_ARRAY_SIZE(emu_tasks)]; t++) { + if (!ets_loop_dont_feed_sw_wdt) { + system_soft_wdt_feed(); + } + ets_intr_lock(); + // printf("etc_loop_iter: "); dump_task(t - emu_tasks + FIRST_PRIO, t); + if (t->i_get != t->i_put) { + progress = true; + // printf("#%d Calling task %d(%p) (%x, %x)\n", cnt++, + // t - emu_tasks + FIRST_PRIO, t->task, t->queue[t->i_get].sig, t->queue[t->i_get].par); + int idx = t->i_get; + if (t->i_put == -1) { + t->i_put = t->i_get; + } + if (++t->i_get == t->qlen) { + t->i_get = 0; + } + // ets_intr_unlock(); + uint32_t old_sw_wdt = *sw_wdt; + t->task(&t->queue[idx]); + if (ets_loop_dont_feed_sw_wdt) { + // Restore previous SW WDT counter, in case task fed/cleared it + *sw_wdt = old_sw_wdt; + } + // ets_intr_lock(); + // printf("Done calling task %d\n", t - emu_tasks + FIRST_PRIO); + } + ets_intr_unlock(); + } + + if (!progress && idle_cb) { + idle_cb(idle_arg); + } + + return progress; +} + +#if SDK_BELOW_1_1_1 +void my_timer_isr(void *arg) { +// uart0_write_char('+'); + ets_post(0x1f, 0, 0); +} + +// Timer init func is in ROM, and calls ets_task by relative addr directly in ROM +// so, we have to re-init task using our handler +void ets_timer_init() { + printf("ets_timer_init\n"); +// _ets_timer_init(); + ets_isr_attach(10, my_timer_isr, NULL); + SET_PERI_REG_MASK(0x3FF00004, 4); + ETS_INTR_ENABLE(10); + ets_task((os_task_t)0x40002E3C, 0x1f, (os_event_t *)0x3FFFDDC0, 4); + + WRITE_PERI_REG(PERIPHS_TIMER_BASEDDR + 0x30, 0); + WRITE_PERI_REG(PERIPHS_TIMER_BASEDDR + 0x28, 0x88); + WRITE_PERI_REG(PERIPHS_TIMER_BASEDDR + 0x30, 0); + printf("Installed timer ISR\n"); +} +#endif + +bool ets_run(void) { + #if USE_ETS_TASK + #if SDK_BELOW_1_1_1 + ets_isr_attach(10, my_timer_isr, NULL); + #endif + _ets_run(); + #else +// ets_timer_init(); + *(char *)0x3FFFC6FC = 0; + ets_intr_lock(); + printf("ets_alt_task: ets_run\n"); + #if DEBUG + dump_tasks(); + #endif + ets_intr_unlock(); + while (1) { + if (!ets_loop_iter()) { + // printf("idle\n"); + ets_intr_lock(); + if (idle_cb) { + idle_cb(idle_arg); + } + asm ("waiti 0"); + ets_intr_unlock(); + } + } + #endif +} + +void ets_set_idle_cb(void (*handler)(void *), void *arg) { + // printf("ets_set_idle_cb(%p, %p)\n", handler, arg); + idle_cb = handler; + idle_arg = arg; +} diff --git a/ports/esp8266/ets_alt_task.h b/ports/esp8266/ets_alt_task.h new file mode 100644 index 0000000000..7eb8ff3a54 --- /dev/null +++ b/ports/esp8266/ets_alt_task.h @@ -0,0 +1,12 @@ +#ifndef MICROPY_INCLUDED_ESP8266_ETS_ALT_TASK_H +#define MICROPY_INCLUDED_ESP8266_ETS_ALT_TASK_H + +extern int ets_loop_iter_disable; +extern int ets_loop_dont_feed_sw_wdt; +extern uint32_t system_time_low_word; +extern uint32_t system_time_high_word; + +void system_time_update(void); +bool ets_loop_iter(void); + +#endif // MICROPY_INCLUDED_ESP8266_ETS_ALT_TASK_H diff --git a/ports/esp8266/etshal.h b/ports/esp8266/etshal.h new file mode 100644 index 0000000000..7d0855a2ae --- /dev/null +++ b/ports/esp8266/etshal.h @@ -0,0 +1,27 @@ +#ifndef MICROPY_INCLUDED_ESP8266_ETSHAL_H +#define MICROPY_INCLUDED_ESP8266_ETSHAL_H + +#include + +// see http://esp8266-re.foogod.com/wiki/Random_Number_Generator +#define WDEV_HWRNG ((volatile uint32_t *)0x3ff20e44) + +void ets_isr_mask(uint32_t mask); +void ets_isr_unmask(uint32_t mask); + +void ets_wdt_disable(void); + +// Opaque structure +#ifndef MD5_CTX +typedef char MD5_CTX[88]; +#endif + +void MD5Init(MD5_CTX *context); +void MD5Update(MD5_CTX *context, const void *data, unsigned int len); +void MD5Final(unsigned char digest[16], MD5_CTX *context); + +uint32_t SPIRead(uint32_t offset, void *buf, uint32_t len); +uint32_t SPIWrite(uint32_t offset, const void *buf, uint32_t len); +uint32_t SPIEraseSector(int sector); + +#endif // MICROPY_INCLUDED_ESP8266_ETSHAL_H diff --git a/ports/esp8266/fatfs_port.c b/ports/esp8266/fatfs_port.c new file mode 100644 index 0000000000..3ffc90040c --- /dev/null +++ b/ports/esp8266/fatfs_port.c @@ -0,0 +1,43 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014, 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/obj.h" +#include "shared/timeutils/timeutils.h" +#include "lib/oofatfs/ff.h" +#include "modmachine.h" + +DWORD get_fattime(void) { + + // TODO: Optimize division (there's no HW division support on ESP8266, + // so it's expensive). + uint32_t secs = (uint32_t)(pyb_rtc_get_us_since_epoch() / 1000000); + + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(secs, &tm); + + return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | + ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); +} diff --git a/ports/esp8266/gccollect.c b/ports/esp8266/gccollect.c new file mode 100644 index 0000000000..bca0e030cb --- /dev/null +++ b/ports/esp8266/gccollect.c @@ -0,0 +1,52 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/mpconfig.h" +#include "py/gc.h" +#include "gccollect.h" + +// As we do not have control over the application entry point, there is no way +// to figure out the real stack base on runtime, so it needs to be hardcoded +#define STACK_END 0x40000000 + +mp_uint_t gc_helper_get_regs_and_sp(mp_uint_t *regs); + +void gc_collect(void) { + // start the GC + gc_collect_start(); + + // get the registers and the sp + mp_uint_t regs[8]; + mp_uint_t sp = gc_helper_get_regs_and_sp(regs); + + // trace the stack, including the registers (since they live on the stack in this function) + gc_collect_root((void **)sp, (STACK_END - sp) / sizeof(uint32_t)); + + // end the GC + gc_collect_end(); +} diff --git a/ports/esp8266/gccollect.h b/ports/esp8266/gccollect.h new file mode 100644 index 0000000000..b86d3d6e1c --- /dev/null +++ b/ports/esp8266/gccollect.h @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP8266_GCCOLLECT_H +#define MICROPY_INCLUDED_ESP8266_GCCOLLECT_H + +#include + +extern uint32_t _text_start; +extern uint32_t _text_end; +extern uint32_t _irom0_text_start; +extern uint32_t _irom0_text_end; +extern uint32_t _data_start; +extern uint32_t _data_end; +extern uint32_t _rodata_start; +extern uint32_t _rodata_end; +extern uint32_t _bss_start; +extern uint32_t _bss_end; +extern uint32_t _heap_start; +extern uint32_t _heap_end; + +void gc_collect(void); + +#endif // MICROPY_INCLUDED_ESP8266_GCCOLLECT_H diff --git a/ports/esp8266/gchelper.s b/ports/esp8266/gchelper.s new file mode 100644 index 0000000000..cf543be800 --- /dev/null +++ b/ports/esp8266/gchelper.s @@ -0,0 +1,22 @@ + .file "gchelper.s" + .text + + .align 4 + .global gc_helper_get_regs_and_sp + .type gc_helper_get_regs_and_sp, @function +gc_helper_get_regs_and_sp: + # store regs into given array + s32i.n a8, a2, 0 + s32i.n a9, a2, 4 + s32i.n a10, a2, 8 + s32i.n a11, a2, 12 + s32i.n a12, a2, 16 + s32i.n a13, a2, 20 + s32i.n a14, a2, 24 + s32i.n a15, a2, 28 + + # return the sp + mov a2, a1 + ret.n + + .size gc_helper_get_regs_and_sp, .-gc_helper_get_regs_and_sp diff --git a/ports/esp8266/help.c b/ports/esp8266/help.c new file mode 100644 index 0000000000..4dd586c1a8 --- /dev/null +++ b/ports/esp8266/help.c @@ -0,0 +1,54 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/builtin.h" + +const char esp_help_text[] = + "Welcome to MicroPython!\n" + "\n" + "For online docs please visit http://docs.micropython.org/en/latest/esp8266/ .\n" + "For diagnostic information to include in bug reports execute 'import port_diag'.\n" + "\n" + "Basic WiFi configuration:\n" + "\n" + "import network\n" + "sta_if = network.WLAN(network.STA_IF); sta_if.active(True)\n" + "sta_if.scan() # Scan for available access points\n" + "sta_if.connect(\"\", \"\") # Connect to an AP\n" + "sta_if.isconnected() # Check for successful connection\n" + "# Change name/password of ESP8266's AP:\n" + "ap_if = network.WLAN(network.AP_IF)\n" + "ap_if.config(essid=\"\", authmode=network.AUTH_WPA_WPA2_PSK, password=\"\")\n" + "\n" + "Control commands:\n" + " CTRL-A -- on a blank line, enter raw REPL mode\n" + " CTRL-B -- on a blank line, enter normal REPL mode\n" + " CTRL-C -- interrupt a running program\n" + " CTRL-D -- on a blank line, do a soft reset of the board\n" + " CTRL-E -- on a blank line, enter paste mode\n" + "\n" + "For further help on a specific object, type help(obj)\n" +; diff --git a/ports/esp8266/hspi.c b/ports/esp8266/hspi.c new file mode 100644 index 0000000000..cecf0b755c --- /dev/null +++ b/ports/esp8266/hspi.c @@ -0,0 +1,337 @@ +/* +* The MIT License (MIT) +* +* Copyright (c) 2015 David Ogilvy (MetalPhreak) +* Modified 2016 by Radomir Dopieralski +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include "hspi.h" + +/* +Wrapper to setup HSPI/SPI GPIO pins and default SPI clock + spi_no - SPI (0) or HSPI (1) +Not used in MicroPython. +*/ +void spi_init(uint8_t spi_no) { + spi_init_gpio(spi_no, SPI_CLK_USE_DIV); + spi_clock(spi_no, SPI_CLK_PREDIV, SPI_CLK_CNTDIV); + spi_tx_byte_order(spi_no, SPI_BYTE_ORDER_HIGH_TO_LOW); + spi_rx_byte_order(spi_no, SPI_BYTE_ORDER_HIGH_TO_LOW); + + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_CS_SETUP | SPI_CS_HOLD); + CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_FLASH_MODE); +} + + +/* +Configures SPI mode parameters for clock edge and clock polarity. + spi_no - SPI (0) or HSPI (1) + spi_cpha - (0) Data is valid on clock leading edge + (1) Data is valid on clock trailing edge + spi_cpol - (0) Clock is low when inactive + (1) Clock is high when inactive +For MicroPython this version is different from original. +*/ +void spi_mode(uint8_t spi_no, uint8_t spi_cpha, uint8_t spi_cpol) { + if (spi_cpol) { + SET_PERI_REG_MASK(SPI_PIN(HSPI), SPI_IDLE_EDGE); + } else { + CLEAR_PERI_REG_MASK(SPI_PIN(HSPI), SPI_IDLE_EDGE); + } + if (spi_cpha == spi_cpol) { + // Mode 3 - MOSI is set on falling edge of clock + // Mode 0 - MOSI is set on falling edge of clock + CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_OUT_EDGE); + SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_I_EDGE); + } else { + // Mode 2 - MOSI is set on rising edge of clock + // Mode 1 - MOSI is set on rising edge of clock + SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_OUT_EDGE); + CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_I_EDGE); + } +} + + +/* +Initialise the GPIO pins for use as SPI pins. + spi_no - SPI (0) or HSPI (1) + sysclk_as_spiclk - + SPI_CLK_80MHZ_NODIV (1) if using 80MHz for SPI clock. + SPI_CLK_USE_DIV (0) if using divider for lower speed. +*/ +void spi_init_gpio(uint8_t spi_no, uint8_t sysclk_as_spiclk) { + uint32_t clock_div_flag = 0; + if (sysclk_as_spiclk) { + clock_div_flag = 0x0001; + } + if (spi_no == SPI) { + // Set bit 8 if 80MHz sysclock required + WRITE_PERI_REG(PERIPHS_IO_MUX, 0x005 | (clock_div_flag << 8)); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CLK_U, 1); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CMD_U, 1); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA0_U, 1); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA1_U, 1); + } else if (spi_no == HSPI) { + // Set bit 9 if 80MHz sysclock required + WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105 | (clock_div_flag << 9)); + // GPIO12 is HSPI MISO pin (Master Data In) + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, 2); + // GPIO13 is HSPI MOSI pin (Master Data Out) + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2); + // GPIO14 is HSPI CLK pin (Clock) + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2); + // GPIO15 is HSPI CS pin (Chip Select / Slave Select) + // In MicroPython, we are handling CS ourself in drivers. + // PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 2); + } +} + + +/* +Set up the control registers for the SPI clock + spi_no - SPI (0) or HSPI (1) + prediv - predivider value (actual division value) + cntdiv - postdivider value (actual division value) +Set either divider to 0 to disable all division (80MHz sysclock) +*/ +void spi_clock(uint8_t spi_no, uint16_t prediv, uint8_t cntdiv) { + if (prediv == 0 || cntdiv == 0) { + WRITE_PERI_REG(SPI_CLOCK(spi_no), SPI_CLK_EQU_SYSCLK); + } else { + WRITE_PERI_REG(SPI_CLOCK(spi_no), + (((prediv - 1) & SPI_CLKDIV_PRE) << SPI_CLKDIV_PRE_S) | + (((cntdiv - 1) & SPI_CLKCNT_N) << SPI_CLKCNT_N_S) | + (((cntdiv >> 1) & SPI_CLKCNT_H) << SPI_CLKCNT_H_S) | + ((0 & SPI_CLKCNT_L) << SPI_CLKCNT_L_S) + ); + } +} + + +/* +Setup the byte order for shifting data out of buffer + spi_no - SPI (0) or HSPI (1) + byte_order - + SPI_BYTE_ORDER_HIGH_TO_LOW (1) + Data is sent out starting with Bit31 and down to Bit0 + SPI_BYTE_ORDER_LOW_TO_HIGH (0) + Data is sent out starting with the lowest BYTE, from MSB to LSB, + followed by the second lowest BYTE, from MSB to LSB, followed by + the second highest BYTE, from MSB to LSB, followed by the highest + BYTE, from MSB to LSB 0xABCDEFGH would be sent as 0xGHEFCDAB. +*/ +void spi_tx_byte_order(uint8_t spi_no, uint8_t byte_order) { + if (byte_order) { + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_WR_BYTE_ORDER); + } else { + CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_WR_BYTE_ORDER); + } +} + + +/* +Setup the byte order for shifting data into buffer + spi_no - SPI (0) or HSPI (1) + byte_order - + SPI_BYTE_ORDER_HIGH_TO_LOW (1) + Data is read in starting with Bit31 and down to Bit0 + SPI_BYTE_ORDER_LOW_TO_HIGH (0) + Data is read in starting with the lowest BYTE, from MSB to LSB, + followed by the second lowest BYTE, from MSB to LSB, followed by + the second highest BYTE, from MSB to LSB, followed by the highest + BYTE, from MSB to LSB 0xABCDEFGH would be read as 0xGHEFCDAB +*/ +void spi_rx_byte_order(uint8_t spi_no, uint8_t byte_order) { + if (byte_order) { + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_RD_BYTE_ORDER); + } else { + CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_RD_BYTE_ORDER); + } +} + + +/* +SPI transaction function + spi_no - SPI (0) or HSPI (1) + cmd_bits - actual number of bits to transmit + cmd_data - command data + addr_bits - actual number of bits to transmit + addr_data - address data + dout_bits - actual number of bits to transmit + dout_data - output data + din_bits - actual number of bits to receive +Returns: read data - uint32_t containing read in data only if RX was set + 0 - something went wrong (or actual read data was 0) + 1 - data sent ok (or actual read data is 1) +Note: all data is assumed to be stored in the lower bits of the data variables +(for anything <32 bits). +*/ +uint32_t spi_transaction(uint8_t spi_no, uint8_t cmd_bits, uint16_t cmd_data, + uint32_t addr_bits, uint32_t addr_data, + uint32_t dout_bits, uint32_t dout_data, + uint32_t din_bits, uint32_t dummy_bits) { + while (spi_busy(spi_no)) { + } + ; // Wait for SPI to be ready + +// Enable SPI Functions + // Disable MOSI, MISO, ADDR, COMMAND, DUMMY in case previously set. + CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI | SPI_USR_MISO | + SPI_USR_COMMAND | SPI_USR_ADDR | SPI_USR_DUMMY); + + // Enable functions based on number of bits. 0 bits = disabled. + // This is rather inefficient but allows for a very generic function. + // CMD ADDR and MOSI are set below to save on an extra if statement. + if (din_bits) { + if (dout_bits) { + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_DOUTDIN); + } else { + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MISO); + } + } + if (dummy_bits) { + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_DUMMY); + } + +// Setup Bitlengths + WRITE_PERI_REG(SPI_USER1(spi_no), + // Number of bits in Address + ((addr_bits - 1) & SPI_USR_ADDR_BITLEN) << SPI_USR_ADDR_BITLEN_S | + // Number of bits to Send + ((dout_bits - 1) & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S | + // Number of bits to receive + ((din_bits - 1) & SPI_USR_MISO_BITLEN) << SPI_USR_MISO_BITLEN_S | + // Number of Dummy bits to insert + ((dummy_bits - 1) & SPI_USR_DUMMY_CYCLELEN) << SPI_USR_DUMMY_CYCLELEN_S); + +// Setup Command Data + if (cmd_bits) { + // Enable COMMAND function in SPI module + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_COMMAND); + // Align command data to high bits + uint16_t command = cmd_data << (16 - cmd_bits); + // Swap byte order + command = ((command >> 8) & 0xff) | ((command << 8) & 0xff00); + WRITE_PERI_REG(SPI_USER2(spi_no), ( + (((cmd_bits - 1) & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | + (command & SPI_USR_COMMAND_VALUE) + )); + } + +// Setup Address Data + if (addr_bits) { + // Enable ADDRess function in SPI module + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_ADDR); + // Align address data to high bits + WRITE_PERI_REG(SPI_ADDR(spi_no), addr_data << (32 - addr_bits)); + } + +// Setup DOUT data + if (dout_bits) { + // Enable MOSI function in SPI module + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI); + // Copy data to W0 + if (READ_PERI_REG(SPI_USER(spi_no)) & SPI_WR_BYTE_ORDER) { + WRITE_PERI_REG(SPI_W0(spi_no), dout_data << (32 - dout_bits)); + } else { + uint8_t dout_extra_bits = dout_bits % 8; + + if (dout_extra_bits) { + // If your data isn't a byte multiple (8/16/24/32 bits) and you + // don't have SPI_WR_BYTE_ORDER set, you need this to move the + // non-8bit remainder to the MSBs. Not sure if there's even a use + // case for this, but it's here if you need it... For example, + // 0xDA4 12 bits without SPI_WR_BYTE_ORDER would usually be output + // as if it were 0x0DA4, of which 0xA4, and then 0x0 would be + // shifted out (first 8 bits of low byte, then 4 MSB bits of high + // byte - ie reverse byte order). + // The code below shifts it out as 0xA4 followed by 0xD as you + // might require. + WRITE_PERI_REG(SPI_W0(spi_no), ( + (0xFFFFFFFF << (dout_bits - dout_extra_bits) & dout_data) + << (8 - dout_extra_bits) | + ((0xFFFFFFFF >> (32 - (dout_bits - dout_extra_bits))) + & dout_data) + )); + } else { + WRITE_PERI_REG(SPI_W0(spi_no), dout_data); + } + } + } + +// Begin SPI Transaction + SET_PERI_REG_MASK(SPI_CMD(spi_no), SPI_USR); + +// Return DIN data + if (din_bits) { + while (spi_busy(spi_no)) { + } + ; // Wait for SPI transaction to complete + if (READ_PERI_REG(SPI_USER(spi_no)) & SPI_RD_BYTE_ORDER) { + // Assuming data in is written to MSB. TBC + return READ_PERI_REG(SPI_W0(spi_no)) >> (32 - din_bits); + } else { + // Read in the same way as DOUT is sent. Note existing contents of + // SPI_W0 remain unless overwritten! + return READ_PERI_REG(SPI_W0(spi_no)); + } + return 0; // Something went wrong + } + + // Transaction completed + return 1; // Success +} + + +/* +Just do minimal work needed to send 8 bits. +*/ +inline void spi_tx8fast(uint8_t spi_no, uint8_t dout_data) { + while (spi_busy(spi_no)) { + } + ; // Wait for SPI to be ready + +// Enable SPI Functions + // Disable MOSI, MISO, ADDR, COMMAND, DUMMY in case previously set. + CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI | SPI_USR_MISO | + SPI_USR_COMMAND | SPI_USR_ADDR | SPI_USR_DUMMY); + +// Setup Bitlengths + WRITE_PERI_REG(SPI_USER1(spi_no), + // Number of bits to Send + ((8 - 1) & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S | + // Number of bits to receive + ((8 - 1) & SPI_USR_MISO_BITLEN) << SPI_USR_MISO_BITLEN_S); + + +// Setup DOUT data + // Enable MOSI function in SPI module + SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI); + // Copy data to W0 + if (READ_PERI_REG(SPI_USER(spi_no)) & SPI_WR_BYTE_ORDER) { + WRITE_PERI_REG(SPI_W0(spi_no), dout_data << (32 - 8)); + } else { + WRITE_PERI_REG(SPI_W0(spi_no), dout_data); + } + +// Begin SPI Transaction + SET_PERI_REG_MASK(SPI_CMD(spi_no), SPI_USR); +} diff --git a/ports/esp8266/hspi.h b/ports/esp8266/hspi.h new file mode 100644 index 0000000000..f92a13994d --- /dev/null +++ b/ports/esp8266/hspi.h @@ -0,0 +1,79 @@ +/* +* The MIT License (MIT) +* +* Copyright (c) 2015 David Ogilvy (MetalPhreak) +* Modified 2016 by Radomir Dopieralski +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef SPI_APP_H +#define SPI_APP_H + +#include "hspi_register.h" +#include "ets_sys.h" +#include "osapi.h" +#include "os_type.h" + +// Define SPI hardware modules +#define SPI 0 +#define HSPI 1 + +#define SPI_CLK_USE_DIV 0 +#define SPI_CLK_80MHZ_NODIV 1 + +#define SPI_BYTE_ORDER_HIGH_TO_LOW 1 +#define SPI_BYTE_ORDER_LOW_TO_HIGH 0 + +#ifndef CPU_CLK_FREQ // Should already be defined in eagle_soc.h +#define CPU_CLK_FREQ (80 * 1000000) +#endif + +// Define some default SPI clock settings +#define SPI_CLK_PREDIV 10 +#define SPI_CLK_CNTDIV 2 +#define SPI_CLK_FREQ (CPU_CLK_FREQ / (SPI_CLK_PREDIV * SPI_CLK_CNTDIV)) +// 80 / 20 = 4 MHz + +void spi_init(uint8_t spi_no); +void spi_mode(uint8_t spi_no, uint8_t spi_cpha,uint8_t spi_cpol); +void spi_init_gpio(uint8_t spi_no, uint8_t sysclk_as_spiclk); +void spi_clock(uint8_t spi_no, uint16_t prediv, uint8_t cntdiv); +void spi_tx_byte_order(uint8_t spi_no, uint8_t byte_order); +void spi_rx_byte_order(uint8_t spi_no, uint8_t byte_order); +uint32_t spi_transaction(uint8_t spi_no, uint8_t cmd_bits, uint16_t cmd_data, + uint32_t addr_bits, uint32_t addr_data, + uint32_t dout_bits, uint32_t dout_data, + uint32_t din_bits, uint32_t dummy_bits); +void spi_tx8fast(uint8_t spi_no, uint8_t dout_data); + +// Expansion Macros +#define spi_busy(spi_no) READ_PERI_REG(SPI_CMD(spi_no)) & SPI_USR + +#define spi_txd(spi_no, bits, data) spi_transaction(spi_no, 0, 0, 0, 0, bits, (uint32_t)data, 0, 0) +#define spi_tx8(spi_no, data) spi_transaction(spi_no, 0, 0, 0, 0, 8, (uint32_t)data, 0, 0) +#define spi_tx16(spi_no, data) spi_transaction(spi_no, 0, 0, 0, 0, 16, (uint32_t)data, 0, 0) +#define spi_tx32(spi_no, data) spi_transaction(spi_no, 0, 0, 0, 0, 32, (uint32_t)data, 0, 0) + +#define spi_rxd(spi_no, bits) spi_transaction(spi_no, 0, 0, 0, 0, 0, 0, bits, 0) +#define spi_rx8(spi_no) spi_transaction(spi_no, 0, 0, 0, 0, 0, 0, 8, 0) +#define spi_rx16(spi_no) spi_transaction(spi_no, 0, 0, 0, 0, 0, 0, 16, 0) +#define spi_rx32(spi_no) spi_transaction(spi_no, 0, 0, 0, 0, 0, 0, 32, 0) + +#endif diff --git a/ports/esp8266/hspi_register.h b/ports/esp8266/hspi_register.h new file mode 100644 index 0000000000..50ef2fdca3 --- /dev/null +++ b/ports/esp8266/hspi_register.h @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2010 - 2011 Espressif System + * Modified by David Ogilvy (MetalPhreak) + * Based on original file included in SDK 1.0.0 + * + * Missing defines from previous SDK versions have + * been added and are noted with comments. The + * names of these defines are likely to change. + */ + +// *FORMAT-OFF* + +#ifndef SPI_REGISTER_H_INCLUDED +#define SPI_REGISTER_H_INCLUDED + +#define REG_SPI_BASE(i) (0x60000200-i*0x100) + +#define SPI_CMD(i) (REG_SPI_BASE(i) + 0x0) +#define SPI_FLASH_READ (BIT(31)) //From previous SDK +#define SPI_FLASH_WREN (BIT(30)) //From previous SDK +#define SPI_FLASH_WRDI (BIT(29)) //From previous SDK +#define SPI_FLASH_RDID (BIT(28)) //From previous SDK +#define SPI_FLASH_RDSR (BIT(27)) //From previous SDK +#define SPI_FLASH_WRSR (BIT(26)) //From previous SDK +#define SPI_FLASH_PP (BIT(25)) //From previous SDK +#define SPI_FLASH_SE (BIT(24)) //From previous SDK +#define SPI_FLASH_BE (BIT(23)) //From previous SDK +#define SPI_FLASH_CE (BIT(22)) //From previous SDK +#define SPI_FLASH_DP (BIT(21)) //From previous SDK +#define SPI_FLASH_RES (BIT(20)) //From previous SDK +#define SPI_FLASH_HPM (BIT(19)) //From previous SDK +#define SPI_USR (BIT(18)) + +#define SPI_ADDR(i) (REG_SPI_BASE(i) + 0x4) + +#define SPI_CTRL(i) (REG_SPI_BASE(i) + 0x8) +#define SPI_WR_BIT_ORDER (BIT(26)) +#define SPI_RD_BIT_ORDER (BIT(25)) +#define SPI_QIO_MODE (BIT(24)) +#define SPI_DIO_MODE (BIT(23)) +#define SPI_TWO_BYTE_STATUS_EN (BIT(22)) //From previous SDK +#define SPI_WP_REG (BIT(21)) //From previous SDK +#define SPI_QOUT_MODE (BIT(20)) +#define SPI_SHARE_BUS (BIT(19)) //From previous SDK +#define SPI_HOLD_MODE (BIT(18)) //From previous SDK +#define SPI_ENABLE_AHB (BIT(17)) //From previous SDK +#define SPI_SST_AAI (BIT(16)) //From previous SDK +#define SPI_RESANDRES (BIT(15)) //From previous SDK +#define SPI_DOUT_MODE (BIT(14)) +#define SPI_FASTRD_MODE (BIT(13)) + +#define SPI_CTRL1(i) (REG_SPI_BASE (i) + 0xC) //From previous SDK. Removed _FLASH_ from name to match other registers. +#define SPI_CS_HOLD_DELAY 0x0000000F //Espressif BBS +#define SPI_CS_HOLD_DELAY_S 28 //Espressif BBS +#define SPI_CS_HOLD_DELAY_RES 0x00000FFF //Espressif BBS +#define SPI_CS_HOLD_DELAY_RES_S 16 //Espressif BBS +#define SPI_BUS_TIMER_LIMIT 0x0000FFFF //From previous SDK +#define SPI_BUS_TIMER_LIMIT_S 0 //From previous SDK + + +#define SPI_RD_STATUS(i) (REG_SPI_BASE(i) + 0x10) +#define SPI_STATUS_EXT 0x000000FF //From previous SDK +#define SPI_STATUS_EXT_S 24 //From previous SDK +#define SPI_WB_MODE 0x000000FF //From previous SDK +#define SPI_WB_MODE_S 16 //From previous SDK +#define SPI_FLASH_STATUS_PRO_FLAG (BIT(7)) //From previous SDK +#define SPI_FLASH_TOP_BOT_PRO_FLAG (BIT(5)) //From previous SDK +#define SPI_FLASH_BP2 (BIT(4)) //From previous SDK +#define SPI_FLASH_BP1 (BIT(3)) //From previous SDK +#define SPI_FLASH_BP0 (BIT(2)) //From previous SDK +#define SPI_FLASH_WRENABLE_FLAG (BIT(1)) //From previous SDK +#define SPI_FLASH_BUSY_FLAG (BIT(0)) //From previous SDK + +#define SPI_CTRL2(i) (REG_SPI_BASE(i) + 0x14) +#define SPI_CS_DELAY_NUM 0x0000000F +#define SPI_CS_DELAY_NUM_S 28 +#define SPI_CS_DELAY_MODE 0x00000003 +#define SPI_CS_DELAY_MODE_S 26 +#define SPI_MOSI_DELAY_NUM 0x00000007 +#define SPI_MOSI_DELAY_NUM_S 23 +#define SPI_MOSI_DELAY_MODE 0x00000003 //mode 0 : posedge; data set at positive edge of clk + //mode 1 : negedge + 1 cycle delay, only if freq<10MHz ; data set at negitive edge of clk + //mode 2 : Do not use this mode. +#define SPI_MOSI_DELAY_MODE_S 21 +#define SPI_MISO_DELAY_NUM 0x00000007 +#define SPI_MISO_DELAY_NUM_S 18 +#define SPI_MISO_DELAY_MODE 0x00000003 +#define SPI_MISO_DELAY_MODE_S 16 +#define SPI_CK_OUT_HIGH_MODE 0x0000000F +#define SPI_CK_OUT_HIGH_MODE_S 12 +#define SPI_CK_OUT_LOW_MODE 0x0000000F +#define SPI_CK_OUT_LOW_MODE_S 8 +#define SPI_HOLD_TIME 0x0000000F +#define SPI_HOLD_TIME_S 4 +#define SPI_SETUP_TIME 0x0000000F +#define SPI_SETUP_TIME_S 0 + +#define SPI_CLOCK(i) (REG_SPI_BASE(i) + 0x18) +#define SPI_CLK_EQU_SYSCLK (BIT(31)) +#define SPI_CLKDIV_PRE 0x00001FFF +#define SPI_CLKDIV_PRE_S 18 +#define SPI_CLKCNT_N 0x0000003F +#define SPI_CLKCNT_N_S 12 +#define SPI_CLKCNT_H 0x0000003F +#define SPI_CLKCNT_H_S 6 +#define SPI_CLKCNT_L 0x0000003F +#define SPI_CLKCNT_L_S 0 + +#define SPI_USER(i) (REG_SPI_BASE(i) + 0x1C) +#define SPI_USR_COMMAND (BIT(31)) +#define SPI_USR_ADDR (BIT(30)) +#define SPI_USR_DUMMY (BIT(29)) +#define SPI_USR_MISO (BIT(28)) +#define SPI_USR_MOSI (BIT(27)) +#define SPI_USR_DUMMY_IDLE (BIT(26)) //From previous SDK +#define SPI_USR_MOSI_HIGHPART (BIT(25)) +#define SPI_USR_MISO_HIGHPART (BIT(24)) +#define SPI_USR_PREP_HOLD (BIT(23)) //From previous SDK +#define SPI_USR_CMD_HOLD (BIT(22)) //From previous SDK +#define SPI_USR_ADDR_HOLD (BIT(21)) //From previous SDK +#define SPI_USR_DUMMY_HOLD (BIT(20)) //From previous SDK +#define SPI_USR_DIN_HOLD (BIT(19)) //From previous SDK +#define SPI_USR_DOUT_HOLD (BIT(18)) //From previous SDK +#define SPI_USR_HOLD_POL (BIT(17)) //From previous SDK +#define SPI_SIO (BIT(16)) +#define SPI_FWRITE_QIO (BIT(15)) +#define SPI_FWRITE_DIO (BIT(14)) +#define SPI_FWRITE_QUAD (BIT(13)) +#define SPI_FWRITE_DUAL (BIT(12)) +#define SPI_WR_BYTE_ORDER (BIT(11)) +#define SPI_RD_BYTE_ORDER (BIT(10)) +#define SPI_AHB_ENDIAN_MODE 0x00000003 //From previous SDK +#define SPI_AHB_ENDIAN_MODE_S 8 //From previous SDK +#define SPI_CK_OUT_EDGE (BIT(7)) +#define SPI_CK_I_EDGE (BIT(6)) +#define SPI_CS_SETUP (BIT(5)) +#define SPI_CS_HOLD (BIT(4)) +#define SPI_AHB_USR_COMMAND (BIT(3)) //From previous SDK +#define SPI_FLASH_MODE (BIT(2)) +#define SPI_AHB_USR_COMMAND_4BYTE (BIT(1)) //From previous SDK +#define SPI_DOUTDIN (BIT(0)) //From previous SDK + +//AHB = http://en.wikipedia.org/wiki/Advanced_Microcontroller_Bus_Architecture ? + + +#define SPI_USER1(i) (REG_SPI_BASE(i) + 0x20) +#define SPI_USR_ADDR_BITLEN 0x0000003F +#define SPI_USR_ADDR_BITLEN_S 26 +#define SPI_USR_MOSI_BITLEN 0x000001FF +#define SPI_USR_MOSI_BITLEN_S 17 +#define SPI_USR_MISO_BITLEN 0x000001FF +#define SPI_USR_MISO_BITLEN_S 8 +#define SPI_USR_DUMMY_CYCLELEN 0x000000FF +#define SPI_USR_DUMMY_CYCLELEN_S 0 + +#define SPI_USER2(i) (REG_SPI_BASE(i) + 0x24) +#define SPI_USR_COMMAND_BITLEN 0x0000000F +#define SPI_USR_COMMAND_BITLEN_S 28 +#define SPI_USR_COMMAND_VALUE 0x0000FFFF +#define SPI_USR_COMMAND_VALUE_S 0 + +#define SPI_WR_STATUS(i) (REG_SPI_BASE(i) + 0x28) + //previously defined as SPI_FLASH_USER3. No further info available. + +#define SPI_PIN(i) (REG_SPI_BASE(i) + 0x2C) +#define SPI_IDLE_EDGE (BIT(29)) +#define SPI_CS2_DIS (BIT(2)) +#define SPI_CS1_DIS (BIT(1)) +#define SPI_CS0_DIS (BIT(0)) + +#define SPI_SLAVE(i) (REG_SPI_BASE(i) + 0x30) +#define SPI_SYNC_RESET (BIT(31)) +#define SPI_SLAVE_MODE (BIT(30)) +#define SPI_SLV_WR_RD_BUF_EN (BIT(29)) +#define SPI_SLV_WR_RD_STA_EN (BIT(28)) +#define SPI_SLV_CMD_DEFINE (BIT(27)) +#define SPI_TRANS_CNT 0x0000000F +#define SPI_TRANS_CNT_S 23 +#define SPI_SLV_LAST_STATE 0x00000007 //From previous SDK +#define SPI_SLV_LAST_STATE_S 20 //From previous SDK +#define SPI_SLV_LAST_COMMAND 0x00000007 //From previous SDK +#define SPI_SLV_LAST_COMMAND_S 17 //From previous SDK +#define SPI_CS_I_MODE 0x00000003 //From previous SDK +#define SPI_CS_I_MODE_S 10 //From previous SDK +#define SPI_TRANS_DONE_EN (BIT(9)) +#define SPI_SLV_WR_STA_DONE_EN (BIT(8)) +#define SPI_SLV_RD_STA_DONE_EN (BIT(7)) +#define SPI_SLV_WR_BUF_DONE_EN (BIT(6)) +#define SPI_SLV_RD_BUF_DONE_EN (BIT(5)) +#define SLV_SPI_INT_EN 0x0000001f +#define SLV_SPI_INT_EN_S 5 +#define SPI_TRANS_DONE (BIT(4)) +#define SPI_SLV_WR_STA_DONE (BIT(3)) +#define SPI_SLV_RD_STA_DONE (BIT(2)) +#define SPI_SLV_WR_BUF_DONE (BIT(1)) +#define SPI_SLV_RD_BUF_DONE (BIT(0)) + +#define SPI_SLAVE1(i) (REG_SPI_BASE(i) + 0x34) +#define SPI_SLV_STATUS_BITLEN 0x0000001F +#define SPI_SLV_STATUS_BITLEN_S 27 +#define SPI_SLV_STATUS_FAST_EN (BIT(26)) //From previous SDK +#define SPI_SLV_STATUS_READBACK (BIT(25)) //From previous SDK +#define SPI_SLV_BUF_BITLEN 0x000001FF +#define SPI_SLV_BUF_BITLEN_S 16 +#define SPI_SLV_RD_ADDR_BITLEN 0x0000003F +#define SPI_SLV_RD_ADDR_BITLEN_S 10 +#define SPI_SLV_WR_ADDR_BITLEN 0x0000003F +#define SPI_SLV_WR_ADDR_BITLEN_S 4 +#define SPI_SLV_WRSTA_DUMMY_EN (BIT(3)) +#define SPI_SLV_RDSTA_DUMMY_EN (BIT(2)) +#define SPI_SLV_WRBUF_DUMMY_EN (BIT(1)) +#define SPI_SLV_RDBUF_DUMMY_EN (BIT(0)) + + + +#define SPI_SLAVE2(i) (REG_SPI_BASE(i) + 0x38) +#define SPI_SLV_WRBUF_DUMMY_CYCLELEN 0X000000FF +#define SPI_SLV_WRBUF_DUMMY_CYCLELEN_S 24 +#define SPI_SLV_RDBUF_DUMMY_CYCLELEN 0X000000FF +#define SPI_SLV_RDBUF_DUMMY_CYCLELEN_S 16 +#define SPI_SLV_WRSTR_DUMMY_CYCLELEN 0X000000FF +#define SPI_SLV_WRSTR_DUMMY_CYCLELEN_S 8 +#define SPI_SLV_RDSTR_DUMMY_CYCLELEN 0x000000FF +#define SPI_SLV_RDSTR_DUMMY_CYCLELEN_S 0 + +#define SPI_SLAVE3(i) (REG_SPI_BASE(i) + 0x3C) +#define SPI_SLV_WRSTA_CMD_VALUE 0x000000FF +#define SPI_SLV_WRSTA_CMD_VALUE_S 24 +#define SPI_SLV_RDSTA_CMD_VALUE 0x000000FF +#define SPI_SLV_RDSTA_CMD_VALUE_S 16 +#define SPI_SLV_WRBUF_CMD_VALUE 0x000000FF +#define SPI_SLV_WRBUF_CMD_VALUE_S 8 +#define SPI_SLV_RDBUF_CMD_VALUE 0x000000FF +#define SPI_SLV_RDBUF_CMD_VALUE_S 0 + +//Previous SDKs referred to these following registers as SPI_C0 etc. + +#define SPI_W0(i) (REG_SPI_BASE(i) +0x40) +#define SPI_W1(i) (REG_SPI_BASE(i) +0x44) +#define SPI_W2(i) (REG_SPI_BASE(i) +0x48) +#define SPI_W3(i) (REG_SPI_BASE(i) +0x4C) +#define SPI_W4(i) (REG_SPI_BASE(i) +0x50) +#define SPI_W5(i) (REG_SPI_BASE(i) +0x54) +#define SPI_W6(i) (REG_SPI_BASE(i) +0x58) +#define SPI_W7(i) (REG_SPI_BASE(i) +0x5C) +#define SPI_W8(i) (REG_SPI_BASE(i) +0x60) +#define SPI_W9(i) (REG_SPI_BASE(i) +0x64) +#define SPI_W10(i) (REG_SPI_BASE(i) +0x68) +#define SPI_W11(i) (REG_SPI_BASE(i) +0x6C) +#define SPI_W12(i) (REG_SPI_BASE(i) +0x70) +#define SPI_W13(i) (REG_SPI_BASE(i) +0x74) +#define SPI_W14(i) (REG_SPI_BASE(i) +0x78) +#define SPI_W15(i) (REG_SPI_BASE(i) +0x7C) + + // +0x80 to +0xBC could be SPI_W16 through SPI_W31? + + // +0xC0 to +0xEC not currently defined. + +#define SPI_EXT0(i) (REG_SPI_BASE(i) + 0xF0) //From previous SDK. Removed _FLASH_ from name to match other registers. +#define SPI_T_PP_ENA (BIT(31)) //From previous SDK +#define SPI_T_PP_SHIFT 0x0000000F //From previous SDK +#define SPI_T_PP_SHIFT_S 16 //From previous SDK +#define SPI_T_PP_TIME 0x00000FFF //From previous SDK +#define SPI_T_PP_TIME_S 0 //From previous SDK + +#define SPI_EXT1(i) (REG_SPI_BASE(i) + 0xF4) //From previous SDK. Removed _FLASH_ from name to match other registers. +#define SPI_T_ERASE_ENA (BIT(31)) //From previous SDK +#define SPI_T_ERASE_SHIFT 0x0000000F //From previous SDK +#define SPI_T_ERASE_SHIFT_S 16 //From previous SDK +#define SPI_T_ERASE_TIME 0x00000FFF //From previous SDK +#define SPI_T_ERASE_TIME_S 0 //From previous SDK + +#define SPI_EXT2(i) (REG_SPI_BASE(i) + 0xF8) //From previous SDK. Removed _FLASH_ from name to match other registers. +#define SPI_ST 0x00000007 //From previous SDK +#define SPI_ST_S 0 //From previous SDK + +#define SPI_EXT3(i) (REG_SPI_BASE(i) + 0xFC) +#define SPI_INT_HOLD_ENA 0x00000003 +#define SPI_INT_HOLD_ENA_S 0 +#endif // SPI_REGISTER_H_INCLUDED diff --git a/ports/esp8266/lexerstr32.c b/ports/esp8266/lexerstr32.c new file mode 100644 index 0000000000..a921efbbdc --- /dev/null +++ b/ports/esp8266/lexerstr32.c @@ -0,0 +1,69 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2016 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/lexer.h" + +#if MICROPY_ENABLE_COMPILER + +typedef struct _mp_lexer_str32_buf_t { + const uint32_t *src_cur; + uint32_t val; + uint8_t byte_off; +} mp_lexer_str32_buf_t; + +STATIC mp_uint_t str32_buf_next_byte(void *sb_in) { + mp_lexer_str32_buf_t *sb = (mp_lexer_str32_buf_t *)sb_in; + byte c = sb->val & 0xff; + if (c == 0) { + return MP_READER_EOF; + } + + if (++sb->byte_off > 3) { + sb->byte_off = 0; + sb->val = *sb->src_cur++; + } else { + sb->val >>= 8; + } + + return c; +} + +STATIC void str32_buf_free(void *sb_in) { + mp_lexer_str32_buf_t *sb = (mp_lexer_str32_buf_t *)sb_in; + m_del_obj(mp_lexer_str32_buf_t, sb); +} + +mp_lexer_t *mp_lexer_new_from_str32(qstr src_name, const char *str, mp_uint_t len, mp_uint_t free_len) { + mp_lexer_str32_buf_t *sb = m_new_obj(mp_lexer_str32_buf_t); + sb->byte_off = (uint32_t)str & 3; + sb->src_cur = (uint32_t *)(str - sb->byte_off); + sb->val = *sb->src_cur++ >> sb->byte_off * 8; + mp_reader_t reader = {sb, str32_buf_next_byte, str32_buf_free}; + return mp_lexer_new(src_name, reader); +} + +#endif // MICROPY_ENABLE_COMPILER diff --git a/ports/esp8266/machine_adc.c b/ports/esp8266/machine_adc.c new file mode 100644 index 0000000000..471e14d8df --- /dev/null +++ b/ports/esp8266/machine_adc.c @@ -0,0 +1,98 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Josef Gajdusek + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/runtime.h" +#include "py/mphal.h" +#include "user_interface.h" + +typedef struct _machine_adc_obj_t { + mp_obj_base_t base; + bool isvdd; +} machine_adc_obj_t; + +extern const mp_obj_type_t machine_adc_type; + +STATIC machine_adc_obj_t machine_adc_vdd3 = {{&machine_adc_type}, true}; +STATIC machine_adc_obj_t machine_adc_adc = {{&machine_adc_type}, false}; + +STATIC uint16_t adc_read(machine_adc_obj_t *self) { + if (self->isvdd) { + return system_get_vdd33(); + } else { + return system_adc_read(); + } +} +void machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "ADC(%u)", self->isvdd); +} + +mp_obj_t machine_adc_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_int_t chn = mp_obj_get_int(args[0]); + + switch (chn) { + case 0: + return &machine_adc_adc; + case 1: + return &machine_adc_vdd3; + default: + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("ADC(%d) doesn't exist"), chn); + } +} + +// read_u16() +STATIC mp_obj_t machine_adc_read_u16(mp_obj_t self_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint32_t value = adc_read(self); + return MP_OBJ_NEW_SMALL_INT(value * 65535 / 1024); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_read_u16_obj, machine_adc_read_u16); + +// Legacy method +STATIC mp_obj_t machine_adc_read(mp_obj_t self_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(adc_read(self)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_read_obj, machine_adc_read); + +STATIC const mp_rom_map_elem_t machine_adc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_read_u16), MP_ROM_PTR(&machine_adc_read_u16_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&machine_adc_read_obj) } +}; +STATIC MP_DEFINE_CONST_DICT(machine_adc_locals_dict, machine_adc_locals_dict_table); + +const mp_obj_type_t machine_adc_type = { + { &mp_type_type }, + .name = MP_QSTR_ADC, + .print = machine_adc_print, + .make_new = machine_adc_make_new, + .locals_dict = (mp_obj_dict_t *)&machine_adc_locals_dict, +}; diff --git a/ports/esp8266/machine_bitstream.c b/ports/esp8266/machine_bitstream.c new file mode 100644 index 0000000000..13666f3d0f --- /dev/null +++ b/ports/esp8266/machine_bitstream.c @@ -0,0 +1,74 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// This is slightly modified from the implementation in ports/esp32/machine_bitstream.c. + +#include "py/mpconfig.h" +#include "py/mphal.h" + +#if MICROPY_PY_MACHINE_BITSTREAM + +#define NS_TICKS_OVERHEAD (8) + +void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { + uint32_t pin_mask = 1 << pin; + + // Convert ns to cpu ticks [high_time_0, period_0, high_time_1, period_1]. + uint32_t fcpu_mhz = system_get_cpu_freq(); + for (size_t i = 0; i < 4; ++i) { + timing_ns[i] = fcpu_mhz * timing_ns[i] / 1000; + if (timing_ns[i] > NS_TICKS_OVERHEAD) { + timing_ns[i] -= NS_TICKS_OVERHEAD; + } + if (i % 2 == 1) { + // Convert low_time to period (i.e. add high_time). + timing_ns[i] += timing_ns[i - 1]; + } + } + + uint32_t irq_state = mp_hal_quiet_timing_enter(); + + for (size_t i = 0; i < len; ++i) { + uint8_t b = buf[i]; + for (size_t j = 0; j < 8; ++j) { + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pin_mask); + uint32_t start_ticks = mp_hal_ticks_cpu(); + uint32_t *t = &timing_ns[b >> 6 & 2]; + while (mp_hal_ticks_cpu() - start_ticks < t[0]) { + ; + } + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pin_mask); + b <<= 1; + while (mp_hal_ticks_cpu() - start_ticks < t[1]) { + ; + } + } + } + + mp_hal_quiet_timing_exit(irq_state); +} + +#endif // MICROPY_PY_MACHINE_BITSTREAM diff --git a/ports/esp8266/machine_hspi.c b/ports/esp8266/machine_hspi.c new file mode 100644 index 0000000000..ff3ba17255 --- /dev/null +++ b/ports/esp8266/machine_hspi.c @@ -0,0 +1,188 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "ets_sys.h" +#include "etshal.h" +#include "ets_alt_task.h" + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mphal.h" +#include "extmod/machine_spi.h" +#include "modmachine.h" +#include "hspi.h" + +#if MICROPY_PY_MACHINE_SPI + +typedef struct _machine_hspi_obj_t { + mp_obj_base_t base; + uint32_t baudrate; + uint8_t polarity; + uint8_t phase; +} machine_hspi_obj_t; + +STATIC void machine_hspi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) { + (void)self_in; + + if (dest == NULL) { + // fast case when we only need to write data + size_t chunk_size = 1024; + size_t count = len / chunk_size; + size_t i = 0; + for (size_t j = 0; j < count; ++j) { + for (size_t k = 0; k < chunk_size; ++k) { + spi_tx8fast(HSPI, src[i]); + ++i; + } + ets_loop_iter(); + } + while (i < len) { + spi_tx8fast(HSPI, src[i]); + ++i; + } + // wait for SPI transaction to complete + while (spi_busy(HSPI)) { + } + } else { + // we need to read and write data + + // Process data in chunks, let the pending tasks run in between + size_t chunk_size = 1024; // TODO this should depend on baudrate + size_t count = len / chunk_size; + size_t i = 0; + for (size_t j = 0; j < count; ++j) { + for (size_t k = 0; k < chunk_size; ++k) { + dest[i] = spi_transaction(HSPI, 0, 0, 0, 0, 8, src[i], 8, 0); + ++i; + } + ets_loop_iter(); + } + while (i < len) { + dest[i] = spi_transaction(HSPI, 0, 0, 0, 0, 8, src[i], 8, 0); + ++i; + } + } +} + +/******************************************************************************/ +// MicroPython bindings for HSPI + +STATIC void machine_hspi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_hspi_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "HSPI(id=1, baudrate=%u, polarity=%u, phase=%u)", + self->baudrate, self->polarity, self->phase); +} + +STATIC void machine_hspi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + machine_hspi_obj_t *self = (machine_hspi_obj_t *)self_in; + + enum { ARG_baudrate, ARG_polarity, ARG_phase }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_polarity, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_phase, MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), + allowed_args, args); + + if (args[ARG_baudrate].u_int != -1) { + self->baudrate = args[ARG_baudrate].u_int; + } + if (args[ARG_polarity].u_int != -1) { + self->polarity = args[ARG_polarity].u_int; + } + if (args[ARG_phase].u_int != -1) { + self->phase = args[ARG_phase].u_int; + } + if (self->baudrate == 80000000L) { + // Special case for full speed. + spi_init_gpio(HSPI, SPI_CLK_80MHZ_NODIV); + spi_clock(HSPI, 0, 0); + } else if (self->baudrate > 40000000L) { + mp_raise_ValueError(MP_ERROR_TEXT("impossible baudrate")); + } else { + uint32_t divider = 40000000L / self->baudrate; + uint16_t prediv = MIN(divider, SPI_CLKDIV_PRE + 1); + uint16_t cntdiv = (divider / prediv) * 2; // cntdiv has to be even + if (cntdiv > SPI_CLKCNT_N + 1 || cntdiv == 0 || prediv == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("impossible baudrate")); + } + self->baudrate = 80000000L / (prediv * cntdiv); + spi_init_gpio(HSPI, SPI_CLK_USE_DIV); + spi_clock(HSPI, prediv, cntdiv); + } + // TODO: Make the byte order configurable too (discuss param names) + spi_tx_byte_order(HSPI, SPI_BYTE_ORDER_HIGH_TO_LOW); + spi_rx_byte_order(HSPI, SPI_BYTE_ORDER_HIGH_TO_LOW); + CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_FLASH_MODE | SPI_USR_MISO | + SPI_USR_ADDR | SPI_USR_COMMAND | SPI_USR_DUMMY); + // Clear Dual or Quad lines transmission mode + CLEAR_PERI_REG_MASK(SPI_CTRL(HSPI), SPI_QIO_MODE | SPI_DIO_MODE | + SPI_DOUT_MODE | SPI_QOUT_MODE); + spi_mode(HSPI, self->phase, self->polarity); +} + +mp_obj_t machine_hspi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, args); + + // args[0] holds the id of the peripheral + if (args[0] != MP_OBJ_NEW_SMALL_INT(1)) { + // FlashROM is on SPI0, so far we don't support its usage + mp_raise_ValueError(NULL); + } + + machine_hspi_obj_t *self = m_new_obj(machine_hspi_obj_t); + self->base.type = &machine_hspi_type; + // set defaults + self->baudrate = 80000000L; + self->polarity = 0; + self->phase = 0; + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_hspi_init((mp_obj_base_t *)self, n_args - 1, args + 1, &kw_args); + return MP_OBJ_FROM_PTR(self); +} + +STATIC const mp_machine_spi_p_t machine_hspi_p = { + .init = machine_hspi_init, + .transfer = machine_hspi_transfer, +}; + +const mp_obj_type_t machine_hspi_type = { + { &mp_type_type }, + .name = MP_QSTR_HSPI, + .print = machine_hspi_print, + .make_new = machine_hspi_make_new, + .protocol = &machine_hspi_p, + .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, +}; + +#endif // MICROPY_PY_MACHINE_SPI diff --git a/ports/esp8266/machine_pin.c b/ports/esp8266/machine_pin.c new file mode 100644 index 0000000000..419ee87a73 --- /dev/null +++ b/ports/esp8266/machine_pin.c @@ -0,0 +1,517 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014, 2015 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "etshal.h" +#include "c_types.h" +#include "user_interface.h" +#include "gpio.h" + +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mphal.h" +#include "extmod/virtpin.h" +#include "modmachine.h" + +#define GET_TRIGGER(phys_port) \ + GPIO_PIN_INT_TYPE_GET(GPIO_REG_READ(GPIO_PIN_ADDR(phys_port))) +#define SET_TRIGGER(phys_port, trig) \ + (GPIO_REG_WRITE(GPIO_PIN_ADDR(phys_port), \ + (GPIO_REG_READ(GPIO_PIN_ADDR(phys_port)) & ~GPIO_PIN_INT_TYPE_MASK) \ + | GPIO_PIN_INT_TYPE_SET(trig))) \ + +#define GPIO_MODE_INPUT (0) +#define GPIO_MODE_OUTPUT (1) +#define GPIO_MODE_OPEN_DRAIN (2) // synthesised +#define GPIO_PULL_NONE (0) +#define GPIO_PULL_UP (1) +// Removed in SDK 1.1.0 +// #define GPIO_PULL_DOWN (2) + +typedef struct _pin_irq_obj_t { + mp_obj_base_t base; + uint16_t phys_port; +} pin_irq_obj_t; + +STATIC void pin_intr_handler_part1(void *arg); +STATIC void pin_intr_handler_part2(uint32_t status); + +const pyb_pin_obj_t pyb_pin_obj[16 + 1] = { + {{&pyb_pin_type}, 0, FUNC_GPIO0, PERIPHS_IO_MUX_GPIO0_U}, + {{&pyb_pin_type}, 1, FUNC_GPIO1, PERIPHS_IO_MUX_U0TXD_U}, + {{&pyb_pin_type}, 2, FUNC_GPIO2, PERIPHS_IO_MUX_GPIO2_U}, + {{&pyb_pin_type}, 3, FUNC_GPIO3, PERIPHS_IO_MUX_U0RXD_U}, + {{&pyb_pin_type}, 4, FUNC_GPIO4, PERIPHS_IO_MUX_GPIO4_U}, + {{&pyb_pin_type}, 5, FUNC_GPIO5, PERIPHS_IO_MUX_GPIO5_U}, + {{NULL}, 0, 0, 0}, + {{NULL}, 0, 0, 0}, + {{NULL}, 0, 0, 0}, + {{&pyb_pin_type}, 9, FUNC_GPIO9, PERIPHS_IO_MUX_SD_DATA2_U}, + {{&pyb_pin_type}, 10, FUNC_GPIO10, PERIPHS_IO_MUX_SD_DATA3_U}, + {{NULL}, 0, 0, 0}, + {{&pyb_pin_type}, 12, FUNC_GPIO12, PERIPHS_IO_MUX_MTDI_U}, + {{&pyb_pin_type}, 13, FUNC_GPIO13, PERIPHS_IO_MUX_MTCK_U}, + {{&pyb_pin_type}, 14, FUNC_GPIO14, PERIPHS_IO_MUX_MTMS_U}, + {{&pyb_pin_type}, 15, FUNC_GPIO15, PERIPHS_IO_MUX_MTDO_U}, + // GPIO16 is special, belongs to different register set, and + // otherwise handled specially. + {{&pyb_pin_type}, 16, -1, -1}, +}; + +STATIC uint8_t pin_mode[16 + 1]; + +// forward declaration +STATIC const pin_irq_obj_t pin_irq_obj[16]; + +void pin_init0(void) { + ETS_GPIO_INTR_DISABLE(); + ETS_GPIO_INTR_ATTACH(pin_intr_handler_part1, NULL); + // disable all interrupts + memset(&MP_STATE_PORT(pin_irq_handler)[0], 0, 16 * sizeof(mp_obj_t)); + for (int p = 0; p < 16; ++p) { + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << p); + SET_TRIGGER(p, 0); + } + ETS_GPIO_INTR_ENABLE(); +} + +void MP_FASTCODE(pin_intr_handler_part1)(void *arg) { + uint32_t status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, status); + pin_intr_handler_part2(status); +} + +void MP_FASTCODE(pin_intr_handler_part2)(uint32_t status) { + status &= 0xffff; + for (int p = 0; status; ++p, status >>= 1) { + if (status & 1) { + mp_obj_t handler = MP_STATE_PORT(pin_irq_handler)[p]; + if (handler != MP_OBJ_NULL) { + mp_sched_schedule(handler, MP_OBJ_FROM_PTR(&pyb_pin_obj[p])); + } + } + } +} + +pyb_pin_obj_t *mp_obj_get_pin_obj(mp_obj_t pin_in) { + if (mp_obj_get_type(pin_in) != &pyb_pin_type) { + mp_raise_ValueError(MP_ERROR_TEXT("expecting a pin")); + } + pyb_pin_obj_t *self = pin_in; + return self; +} + +uint mp_obj_get_pin(mp_obj_t pin_in) { + return mp_obj_get_pin_obj(pin_in)->phys_port; +} + +void mp_hal_pin_input(mp_hal_pin_obj_t pin_id) { + pin_mode[pin_id] = GPIO_MODE_INPUT; + if (pin_id == 16) { + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | 1); + WRITE_PERI_REG(RTC_GPIO_CONF, READ_PERI_REG(RTC_GPIO_CONF) & ~1); + WRITE_PERI_REG(RTC_GPIO_ENABLE, (READ_PERI_REG(RTC_GPIO_ENABLE) & ~1)); // input + } else { + const pyb_pin_obj_t *self = &pyb_pin_obj[pin_id]; + PIN_FUNC_SELECT(self->periph, self->func); + PIN_PULLUP_DIS(self->periph); + gpio_output_set(0, 0, 0, 1 << self->phys_port); + } +} + +void mp_hal_pin_output(mp_hal_pin_obj_t pin_id) { + pin_mode[pin_id] = GPIO_MODE_OUTPUT; + if (pin_id == 16) { + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | 1); + WRITE_PERI_REG(RTC_GPIO_CONF, READ_PERI_REG(RTC_GPIO_CONF) & ~1); + WRITE_PERI_REG(RTC_GPIO_ENABLE, (READ_PERI_REG(RTC_GPIO_ENABLE) & ~1) | 1); // output + } else { + const pyb_pin_obj_t *self = &pyb_pin_obj[pin_id]; + PIN_FUNC_SELECT(self->periph, self->func); + PIN_PULLUP_DIS(self->periph); + gpio_output_set(0, 0, 1 << self->phys_port, 0); + } +} + +void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin_id) { + const pyb_pin_obj_t *pin = &pyb_pin_obj[pin_id]; + + if (pin->phys_port == 16) { + // configure GPIO16 as input with output register holding 0 + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | 1); + WRITE_PERI_REG(RTC_GPIO_CONF, READ_PERI_REG(RTC_GPIO_CONF) & ~1); + WRITE_PERI_REG(RTC_GPIO_ENABLE, (READ_PERI_REG(RTC_GPIO_ENABLE) & ~1)); // input + WRITE_PERI_REG(RTC_GPIO_OUT, (READ_PERI_REG(RTC_GPIO_OUT) & ~1)); // out=0 + return; + } + + ETS_GPIO_INTR_DISABLE(); + PIN_FUNC_SELECT(pin->periph, pin->func); + GPIO_REG_WRITE(GPIO_PIN_ADDR(GPIO_ID_PIN(pin->phys_port)), + GPIO_REG_READ(GPIO_PIN_ADDR(GPIO_ID_PIN(pin->phys_port))) + | GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_ENABLE)); // open drain + GPIO_REG_WRITE(GPIO_ENABLE_ADDRESS, + GPIO_REG_READ(GPIO_ENABLE_ADDRESS) | (1 << pin->phys_port)); + ETS_GPIO_INTR_ENABLE(); +} + +int pin_get(uint pin) { + if (pin == 16) { + return READ_PERI_REG(RTC_GPIO_IN_DATA) & 1; + } + return GPIO_INPUT_GET(pin); +} + +void pin_set(uint pin, int value) { + if (pin == 16) { + int out_en = (pin_mode[pin] == GPIO_MODE_OUTPUT); + WRITE_PERI_REG(PAD_XPD_DCDC_CONF, (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | 1); + WRITE_PERI_REG(RTC_GPIO_CONF, READ_PERI_REG(RTC_GPIO_CONF) & ~1); + WRITE_PERI_REG(RTC_GPIO_ENABLE, (READ_PERI_REG(RTC_GPIO_ENABLE) & ~1) | out_en); + WRITE_PERI_REG(RTC_GPIO_OUT, (READ_PERI_REG(RTC_GPIO_OUT) & ~1) | value); + return; + } + + uint32_t enable = 0; + uint32_t disable = 0; + switch (pin_mode[pin]) { + case GPIO_MODE_INPUT: + value = -1; + disable = 1; + break; + + case GPIO_MODE_OUTPUT: + enable = 1; + break; + + case GPIO_MODE_OPEN_DRAIN: + if (value == -1) { + return; + } else if (value == 0) { + enable = 1; + } else { + value = -1; + disable = 1; + } + break; + } + + enable <<= pin; + disable <<= pin; + if (value == -1) { + gpio_output_set(0, 0, enable, disable); + } else { + gpio_output_set(value << pin, (1 - value) << pin, enable, disable); + } +} + +STATIC void pyb_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + pyb_pin_obj_t *self = self_in; + + // pin name + mp_printf(print, "Pin(%u)", self->phys_port); +} + +// pin.init(mode, pull=None, *, value) +STATIC mp_obj_t pyb_pin_obj_init_helper(pyb_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_pull, ARG_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_pull, MP_ARG_OBJ, {.u_obj = mp_const_none}}, + { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // get io mode + uint mode = args[ARG_mode].u_int; + + // get pull mode + uint pull = GPIO_PULL_NONE; + if (args[ARG_pull].u_obj != mp_const_none) { + pull = mp_obj_get_int(args[ARG_pull].u_obj); + } + + // get initial value + int value; + if (args[ARG_value].u_obj == MP_OBJ_NULL) { + value = -1; + } else { + value = mp_obj_is_true(args[ARG_value].u_obj); + } + + // save the mode + pin_mode[self->phys_port] = mode; + + // configure the GPIO as requested + if (self->phys_port == 16) { + // only pull-down seems to be supported by the hardware, and + // we only expose pull-up behaviour in software + if (pull != GPIO_PULL_NONE) { + mp_raise_ValueError(MP_ERROR_TEXT("Pin(16) doesn't support pull")); + } + } else { + PIN_FUNC_SELECT(self->periph, self->func); + #if 0 + // Removed in SDK 1.1.0 + if ((pull & GPIO_PULL_DOWN) == 0) { + PIN_PULLDWN_DIS(self->periph); + } + #endif + if ((pull & GPIO_PULL_UP) == 0) { + PIN_PULLUP_DIS(self->periph); + } + #if 0 + if ((pull & GPIO_PULL_DOWN) != 0) { + PIN_PULLDWN_EN(self->periph); + } + #endif + if ((pull & GPIO_PULL_UP) != 0) { + PIN_PULLUP_EN(self->periph); + } + } + + pin_set(self->phys_port, value); + + return mp_const_none; +} + +// constructor(id, ...) +mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // get the wanted pin object + int wanted_pin = mp_obj_get_int(args[0]); + pyb_pin_obj_t *pin = NULL; + if (0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(pyb_pin_obj)) { + pin = (pyb_pin_obj_t *)&pyb_pin_obj[wanted_pin]; + } + if (pin == NULL || pin->base.type == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); + } + + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + pyb_pin_obj_init_helper(pin, n_args - 1, args + 1, &kw_args); + } + + return (mp_obj_t)pin; +} + +// fast method for getting/setting pin value +STATIC mp_obj_t pyb_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + pyb_pin_obj_t *self = self_in; + if (n_args == 0) { + // get pin + return MP_OBJ_NEW_SMALL_INT(pin_get(self->phys_port)); + } else { + // set pin + pin_set(self->phys_port, mp_obj_is_true(args[0])); + return mp_const_none; + } +} + +// pin.init(mode, pull) +STATIC mp_obj_t pyb_pin_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return pyb_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(pyb_pin_init_obj, 1, pyb_pin_obj_init); + +// pin.value([value]) +STATIC mp_obj_t pyb_pin_value(size_t n_args, const mp_obj_t *args) { + return pyb_pin_call(args[0], n_args - 1, 0, args + 1); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_pin_value_obj, 1, 2, pyb_pin_value); + +STATIC mp_obj_t pyb_pin_off(mp_obj_t self_in) { + pyb_pin_obj_t *self = self_in; + pin_set(self->phys_port, 0); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_pin_off_obj, pyb_pin_off); + +STATIC mp_obj_t pyb_pin_on(mp_obj_t self_in) { + pyb_pin_obj_t *self = self_in; + pin_set(self->phys_port, 1); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_pin_on_obj, pyb_pin_on); + +// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING, hard=False) +STATIC mp_obj_t pyb_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = GPIO_PIN_INTR_POSEDGE | GPIO_PIN_INTR_NEGEDGE} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + pyb_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (self->phys_port >= 16) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("pin does not have IRQ capabilities")); + } + if (args[ARG_hard].u_bool) { + mp_raise_ValueError(MP_ERROR_TEXT("hard IRQ not supported")); + } + + if (n_args > 1 || kw_args->used != 0) { + // configure irq + mp_obj_t handler = args[ARG_handler].u_obj; + uint32_t trigger = args[ARG_trigger].u_int; + if (handler == mp_const_none) { + handler = MP_OBJ_NULL; + trigger = 0; + } + ETS_GPIO_INTR_DISABLE(); + MP_STATE_PORT(pin_irq_handler)[self->phys_port] = handler; + SET_TRIGGER(self->phys_port, trigger); + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << self->phys_port); + ETS_GPIO_INTR_ENABLE(); + } + + // return the irq object + return MP_OBJ_FROM_PTR(&pin_irq_obj[self->phys_port]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_pin_irq_obj, 1, pyb_pin_irq); + +STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode); +STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + (void)errcode; + pyb_pin_obj_t *self = self_in; + + switch (request) { + case MP_PIN_READ: { + return pin_get(self->phys_port); + } + case MP_PIN_WRITE: { + pin_set(self->phys_port, arg); + return 0; + } + } + return -1; +} + +STATIC const mp_rom_map_elem_t pyb_pin_locals_dict_table[] = { + // instance methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pyb_pin_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&pyb_pin_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&pyb_pin_off_obj) }, + { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&pyb_pin_on_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&pyb_pin_irq_obj) }, + + // class constants + { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_MODE_INPUT) }, + { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_MODE_OUTPUT) }, + { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(GPIO_MODE_OPEN_DRAIN) }, + { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, + // { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, + + // IRQ triggers, can be or'd together + { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_PIN_INTR_POSEDGE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_PIN_INTR_NEGEDGE) }, +}; + +STATIC MP_DEFINE_CONST_DICT(pyb_pin_locals_dict, pyb_pin_locals_dict_table); + +STATIC const mp_pin_p_t pin_pin_p = { + .ioctl = pin_ioctl, +}; + +const mp_obj_type_t pyb_pin_type = { + { &mp_type_type }, + .name = MP_QSTR_Pin, + .print = pyb_pin_print, + .make_new = mp_pin_make_new, + .call = pyb_pin_call, + .protocol = &pin_pin_p, + .locals_dict = (mp_obj_dict_t *)&pyb_pin_locals_dict, +}; + +/******************************************************************************/ +// Pin IRQ object + +STATIC const mp_obj_type_t pin_irq_type; + +STATIC const pin_irq_obj_t pin_irq_obj[16] = { + {{&pin_irq_type}, 0}, + {{&pin_irq_type}, 1}, + {{&pin_irq_type}, 2}, + {{&pin_irq_type}, 3}, + {{&pin_irq_type}, 4}, + {{&pin_irq_type}, 5}, + {{&pin_irq_type}, 6}, + {{&pin_irq_type}, 7}, + {{&pin_irq_type}, 8}, + {{&pin_irq_type}, 9}, + {{&pin_irq_type}, 10}, + {{&pin_irq_type}, 11}, + {{&pin_irq_type}, 12}, + {{&pin_irq_type}, 13}, + {{&pin_irq_type}, 14}, + {{&pin_irq_type}, 15}, +}; + +STATIC mp_obj_t pin_irq_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + pin_irq_obj_t *self = self_in; + mp_arg_check_num(n_args, n_kw, 0, 0, false); + pin_intr_handler_part2(1 << self->phys_port); + return mp_const_none; +} + +STATIC mp_obj_t pin_irq_trigger(size_t n_args, const mp_obj_t *args) { + pin_irq_obj_t *self = args[0]; + uint32_t orig_trig = GET_TRIGGER(self->phys_port); + if (n_args == 2) { + // set trigger + SET_TRIGGER(self->phys_port, mp_obj_get_int(args[1])); + } + // return original trigger value + return MP_OBJ_NEW_SMALL_INT(orig_trig); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pin_irq_trigger_obj, 1, 2, pin_irq_trigger); + +STATIC const mp_rom_map_elem_t pin_irq_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_trigger), MP_ROM_PTR(&pin_irq_trigger_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(pin_irq_locals_dict, pin_irq_locals_dict_table); + +STATIC const mp_obj_type_t pin_irq_type = { + { &mp_type_type }, + .name = MP_QSTR_IRQ, + .call = pin_irq_call, + .locals_dict = (mp_obj_dict_t *)&pin_irq_locals_dict, +}; diff --git a/ports/esp8266/machine_pwm.c b/ports/esp8266/machine_pwm.c new file mode 100644 index 0000000000..f8cd937b80 --- /dev/null +++ b/ports/esp8266/machine_pwm.c @@ -0,0 +1,135 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "modmachine.h" + +#include "esppwm.h" + +typedef struct _machine_pwm_obj_t { + mp_obj_base_t base; + pyb_pin_obj_t *pin; + uint8_t active; + uint8_t channel; +} machine_pwm_obj_t; + +STATIC bool pwm_inited = false; + +/******************************************************************************/ +// MicroPython bindings for PWM + +STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "PWM(%u", self->pin->phys_port); + if (self->active) { + mp_printf(print, ", freq=%u, duty=%u", + pwm_get_freq(self->channel), pwm_get_duty(self->channel)); + } + mp_printf(print, ")"); +} + +STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_freq, ARG_duty }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty, MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int channel = pwm_add(self->pin->phys_port, self->pin->periph, self->pin->func); + if (channel == -1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on pin %d"), self->pin->phys_port); + } + + self->channel = channel; + self->active = 1; + if (args[ARG_freq].u_int != -1) { + pwm_set_freq(args[ARG_freq].u_int, self->channel); + } + if (args[ARG_duty].u_int != -1) { + pwm_set_duty(args[ARG_duty].u_int, self->channel); + } + + pwm_start(); +} + +STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + pyb_pin_obj_t *pin = mp_obj_get_pin_obj(args[0]); + + // create PWM object from the given pin + machine_pwm_obj_t *self = m_new_obj(machine_pwm_obj_t); + self->base.type = &machine_pwm_type; + self->pin = pin; + self->active = 0; + self->channel = -1; + + // start the PWM subsystem if it's not already running + if (!pwm_inited) { + pwm_init(); + pwm_inited = true; + } + + // start the PWM running for this channel + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { + pwm_delete(self->channel); + self->active = 0; + pwm_start(); +} + +STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(pwm_get_freq(0)); +} + +STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { + pwm_set_freq(freq, 0); + pwm_start(); +} + +STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { + if (!self->active) { + pwm_add(self->pin->phys_port, self->pin->periph, self->pin->func); + self->active = 1; + } + return MP_OBJ_NEW_SMALL_INT(pwm_get_duty(self->channel)); +} + +STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) { + if (!self->active) { + pwm_add(self->pin->phys_port, self->pin->periph, self->pin->func); + self->active = 1; + } + pwm_set_duty(duty, self->channel); + pwm_start(); +} diff --git a/ports/esp8266/machine_rtc.c b/ports/esp8266/machine_rtc.c new file mode 100644 index 0000000000..38049ce724 --- /dev/null +++ b/ports/esp8266/machine_rtc.c @@ -0,0 +1,270 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Josef Gajdusek + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/runtime.h" +#include "shared/timeutils/timeutils.h" +#include "user_interface.h" +#include "modmachine.h" + +typedef struct _pyb_rtc_obj_t { + mp_obj_base_t base; +} pyb_rtc_obj_t; + +#define MEM_MAGIC 0x75507921 +#define MEM_DELTA_ADDR 64 +#define MEM_CAL_ADDR (MEM_DELTA_ADDR + 2) +#define MEM_USER_MAGIC_ADDR (MEM_CAL_ADDR + 1) +#define MEM_USER_LEN_ADDR (MEM_USER_MAGIC_ADDR + 1) +#define MEM_USER_DATA_ADDR (MEM_USER_LEN_ADDR + 1) +#define MEM_USER_MAXLEN (512 - (MEM_USER_DATA_ADDR - MEM_DELTA_ADDR) * 4) + +// singleton RTC object +STATIC const pyb_rtc_obj_t pyb_rtc_obj = {{&pyb_rtc_type}}; + +// ALARM0 state +uint32_t pyb_rtc_alarm0_wake; // see MACHINE_WAKE_xxx constants +uint64_t pyb_rtc_alarm0_expiry; // in microseconds + +// RTC overflow checking +STATIC uint32_t rtc_last_ticks; + +void mp_hal_rtc_init(void) { + uint32_t magic; + + system_rtc_mem_read(MEM_USER_MAGIC_ADDR, &magic, sizeof(magic)); + if (magic != MEM_MAGIC) { + magic = MEM_MAGIC; + system_rtc_mem_write(MEM_USER_MAGIC_ADDR, &magic, sizeof(magic)); + uint32_t cal = system_rtc_clock_cali_proc(); + int64_t delta = 0; + system_rtc_mem_write(MEM_CAL_ADDR, &cal, sizeof(cal)); + system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); + uint32_t len = 0; + system_rtc_mem_write(MEM_USER_LEN_ADDR, &len, sizeof(len)); + } + // system_get_rtc_time() is always 0 after reset/deepsleep + rtc_last_ticks = system_get_rtc_time(); + + // reset ALARM0 state + pyb_rtc_alarm0_wake = 0; + pyb_rtc_alarm0_expiry = 0; +} + +STATIC mp_obj_t pyb_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return constant object + return (mp_obj_t)&pyb_rtc_obj; +} + +void pyb_rtc_set_us_since_epoch(uint64_t nowus) { + uint32_t cal = system_rtc_clock_cali_proc(); + // Save RTC ticks for overflow detection. + rtc_last_ticks = system_get_rtc_time(); + int64_t delta = nowus - (((uint64_t)rtc_last_ticks * cal) >> 12); + + // As the calibration value jitters quite a bit, to make the + // clock at least somewhat practically usable, we need to store it + system_rtc_mem_write(MEM_CAL_ADDR, &cal, sizeof(cal)); + system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); +}; + +uint64_t pyb_rtc_get_us_since_epoch() { + uint32_t cal; + int64_t delta; + uint32_t rtc_ticks; + + system_rtc_mem_read(MEM_CAL_ADDR, &cal, sizeof(cal)); + system_rtc_mem_read(MEM_DELTA_ADDR, &delta, sizeof(delta)); + + // ESP-SDK system_get_rtc_time() only returns uint32 and therefore + // overflow about every 7:45h. Thus, we have to check for + // overflow and handle it. + rtc_ticks = system_get_rtc_time(); + if (rtc_ticks < rtc_last_ticks) { + // Adjust delta because of RTC overflow. + delta += (uint64_t)cal << 20; + system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); + } + rtc_last_ticks = rtc_ticks; + + return (((uint64_t)rtc_ticks * cal) >> 12) + delta; +}; + +void rtc_prepare_deepsleep(uint64_t sleep_us) { + // RTC time will reset at wake up. Let's be preared for this. + int64_t delta = pyb_rtc_get_us_since_epoch() + sleep_us; + system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); +} + +STATIC mp_obj_t pyb_rtc_datetime(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + // Get time + uint64_t msecs = pyb_rtc_get_us_since_epoch() / 1000; + + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(msecs / 1000, &tm); + + mp_obj_t tuple[8] = { + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(tm.tm_wday), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(msecs % 1000) + }; + + return mp_obj_new_tuple(8, tuple); + } else { + // Set time + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[1], 8, &items); + + pyb_rtc_set_us_since_epoch( + ((uint64_t)timeutils_seconds_since_epoch( + mp_obj_get_int(items[0]), + mp_obj_get_int(items[1]), + mp_obj_get_int(items[2]), + mp_obj_get_int(items[4]), + mp_obj_get_int(items[5]), + mp_obj_get_int(items[6])) * 1000 + mp_obj_get_int(items[7])) * 1000); + + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_rtc_datetime_obj, 1, 2, pyb_rtc_datetime); + +STATIC mp_obj_t pyb_rtc_memory(size_t n_args, const mp_obj_t *args) { + uint8_t rtcram[MEM_USER_MAXLEN]; + uint32_t len; + + if (n_args == 1) { + // read RTC memory + + system_rtc_mem_read(MEM_USER_LEN_ADDR, &len, sizeof(len)); + system_rtc_mem_read(MEM_USER_DATA_ADDR, rtcram, (len + 3) & ~3); + + return mp_obj_new_bytes(rtcram, len); + } else { + // write RTC memory + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); + + if (bufinfo.len > MEM_USER_MAXLEN) { + mp_raise_ValueError(MP_ERROR_TEXT("buffer too long")); + } + + len = bufinfo.len; + system_rtc_mem_write(MEM_USER_LEN_ADDR, &len, sizeof(len)); + + int i = 0; + for (; i < bufinfo.len; i++) { + rtcram[i] = ((uint8_t *)bufinfo.buf)[i]; + } + + system_rtc_mem_write(MEM_USER_DATA_ADDR, rtcram, (len + 3) & ~3); + + return mp_const_none; + } + +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_rtc_memory_obj, 1, 2, pyb_rtc_memory); + +STATIC mp_obj_t pyb_rtc_alarm(mp_obj_t self_in, mp_obj_t alarm_id, mp_obj_t time_in) { + (void)self_in; // unused + + // check we want alarm0 + if (mp_obj_get_int(alarm_id) != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid alarm")); + } + + // set expiry time (in microseconds) + pyb_rtc_alarm0_expiry = pyb_rtc_get_us_since_epoch() + (uint64_t)mp_obj_get_int(time_in) * 1000; + + return mp_const_none; + +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(pyb_rtc_alarm_obj, pyb_rtc_alarm); + +STATIC mp_obj_t pyb_rtc_alarm_left(size_t n_args, const mp_obj_t *args) { + // check we want alarm0 + if (n_args > 1 && mp_obj_get_int(args[1]) != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid alarm")); + } + + uint64_t now = pyb_rtc_get_us_since_epoch(); + if (pyb_rtc_alarm0_expiry <= now) { + return MP_OBJ_NEW_SMALL_INT(0); + } else { + return mp_obj_new_int((pyb_rtc_alarm0_expiry - now) / 1000); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_rtc_alarm_left_obj, 1, 2, pyb_rtc_alarm_left); + +STATIC mp_obj_t pyb_rtc_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_trigger, ARG_wake }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_trigger, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_wake, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // check we want alarm0 + if (args[ARG_trigger].u_int != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid alarm")); + } + + // set the wake value + pyb_rtc_alarm0_wake = args[ARG_wake].u_int; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_rtc_irq_obj, 1, pyb_rtc_irq); + +STATIC const mp_rom_map_elem_t pyb_rtc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&pyb_rtc_datetime_obj) }, + { MP_ROM_QSTR(MP_QSTR_memory), MP_ROM_PTR(&pyb_rtc_memory_obj) }, + { MP_ROM_QSTR(MP_QSTR_alarm), MP_ROM_PTR(&pyb_rtc_alarm_obj) }, + { MP_ROM_QSTR(MP_QSTR_alarm_left), MP_ROM_PTR(&pyb_rtc_alarm_left_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&pyb_rtc_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_ALARM0), MP_ROM_INT(0) }, +}; +STATIC MP_DEFINE_CONST_DICT(pyb_rtc_locals_dict, pyb_rtc_locals_dict_table); + +const mp_obj_type_t pyb_rtc_type = { + { &mp_type_type }, + .name = MP_QSTR_RTC, + .make_new = pyb_rtc_make_new, + .locals_dict = (mp_obj_dict_t *)&pyb_rtc_locals_dict, +}; diff --git a/ports/esp8266/machine_uart.c b/ports/esp8266/machine_uart.c new file mode 100644 index 0000000000..a8ea5e38d3 --- /dev/null +++ b/ports/esp8266/machine_uart.c @@ -0,0 +1,332 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "ets_sys.h" +#include "user_interface.h" +#include "uart.h" + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "modmachine.h" + +// UartDev is defined and initialized in rom code. +extern UartDevice UartDev; + +typedef struct _pyb_uart_obj_t { + mp_obj_base_t base; + uint8_t uart_id; + uint8_t bits; + uint8_t parity; + uint8_t stop; + uint32_t baudrate; + uint16_t timeout; // timeout waiting for first char (in ms) + uint16_t timeout_char; // timeout waiting between chars (in ms) +} pyb_uart_obj_t; + +STATIC const char *_parity_name[] = {"None", "1", "0"}; + +/******************************************************************************/ +// MicroPython bindings for UART + +STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, rxbuf=%u, timeout=%u, timeout_char=%u)", + self->uart_id, self->baudrate, self->bits, _parity_name[self->parity], + self->stop, uart0_get_rxbuf_len() - 1, self->timeout, self->timeout_char); +} + +STATIC void pyb_uart_init_helper(pyb_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rxbuf, ARG_timeout, ARG_timeout_char }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_parity, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_stop, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_tx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_rx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // set baudrate + if (args[ARG_baudrate].u_int > 0) { + self->baudrate = args[ARG_baudrate].u_int; + UartDev.baut_rate = self->baudrate; // Sic! + } + + // set data bits + switch (args[ARG_bits].u_int) { + case 0: + break; + case 5: + UartDev.data_bits = UART_FIVE_BITS; + self->bits = 5; + break; + case 6: + UartDev.data_bits = UART_SIX_BITS; + self->bits = 6; + break; + case 7: + UartDev.data_bits = UART_SEVEN_BITS; + self->bits = 7; + break; + case 8: + UartDev.data_bits = UART_EIGHT_BITS; + self->bits = 8; + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid data bits")); + break; + } + + // set parity + if (args[ARG_parity].u_obj != MP_OBJ_NULL) { + if (args[ARG_parity].u_obj == mp_const_none) { + UartDev.parity = UART_NONE_BITS; + UartDev.exist_parity = UART_STICK_PARITY_DIS; + self->parity = 0; + } else { + mp_int_t parity = mp_obj_get_int(args[ARG_parity].u_obj); + UartDev.exist_parity = UART_STICK_PARITY_EN; + if (parity & 1) { + UartDev.parity = UART_ODD_BITS; + self->parity = 1; + } else { + UartDev.parity = UART_EVEN_BITS; + self->parity = 2; + } + } + } + + // set tx/rx pins + mp_hal_pin_obj_t tx = 1, rx = 3; + if (args[ARG_tx].u_obj != MP_OBJ_NULL) { + tx = mp_hal_get_pin_obj(args[ARG_tx].u_obj); + } + if (args[ARG_rx].u_obj != MP_OBJ_NULL) { + rx = mp_hal_get_pin_obj(args[ARG_rx].u_obj); + } + if (tx == 1 && rx == 3) { + system_uart_de_swap(); + } else if (tx == 15 && rx == 13) { + system_uart_swap(); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid tx/rx")); + } + + // set stop bits + switch (args[ARG_stop].u_int) { + case 0: + break; + case 1: + UartDev.stop_bits = UART_ONE_STOP_BIT; + self->stop = 1; + break; + case 2: + UartDev.stop_bits = UART_TWO_STOP_BIT; + self->stop = 2; + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid stop bits")); + break; + } + + // set rx ring buffer + if (args[ARG_rxbuf].u_int > 0) { + uint16_t len = args[ARG_rxbuf].u_int + 1; // account for usable items in ringbuf + byte *buf; + if (len <= UART0_STATIC_RXBUF_LEN) { + buf = uart_ringbuf_array; + MP_STATE_PORT(uart0_rxbuf) = NULL; // clear any old pointer + } else { + buf = m_new(byte, len); + MP_STATE_PORT(uart0_rxbuf) = buf; // retain root pointer + } + uart0_set_rxbuf(buf, len); + } + + // set timeout + self->timeout = args[ARG_timeout].u_int; + + // set timeout_char + // make sure it is at least as long as a whole character (13 bits to be safe) + self->timeout_char = args[ARG_timeout_char].u_int; + uint32_t min_timeout_char = 13000 / self->baudrate + 1; + if (self->timeout_char < min_timeout_char) { + self->timeout_char = min_timeout_char; + } + + // setup + uart_setup(self->uart_id); +} + +STATIC mp_obj_t pyb_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // get uart id + mp_int_t uart_id = mp_obj_get_int(args[0]); + if (uart_id != 0 && uart_id != 1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) does not exist"), uart_id); + } + + // create instance + pyb_uart_obj_t *self = m_new_obj(pyb_uart_obj_t); + self->base.type = &pyb_uart_type; + self->uart_id = uart_id; + self->baudrate = 115200; + self->bits = 8; + self->parity = 0; + self->stop = 1; + self->timeout = 0; + self->timeout_char = 0; + + // init the peripheral + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + pyb_uart_init_helper(self, n_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t pyb_uart_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + pyb_uart_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(pyb_uart_init_obj, 1, pyb_uart_init); + +STATIC mp_obj_t pyb_uart_any(mp_obj_t self_in) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(uart_rx_any(self->uart_id)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_uart_any_obj, pyb_uart_any); + +STATIC const mp_rom_map_elem_t pyb_uart_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pyb_uart_init_obj) }, + + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&pyb_uart_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(pyb_uart_locals_dict, pyb_uart_locals_dict_table); + +STATIC mp_uint_t pyb_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->uart_id == 1) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("UART(1) can't read")); + } + + // make sure we want at least 1 char + if (size == 0) { + return 0; + } + + // wait for first char to become available + if (!uart_rx_wait(self->timeout * 1000)) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } + + // read the data + uint8_t *buf = buf_in; + for (;;) { + *buf++ = uart_rx_char(); + if (--size == 0 || !uart_rx_wait(self->timeout_char * 1000)) { + // return number of bytes read + return buf - (uint8_t *)buf_in; + } + } +} + +STATIC mp_uint_t pyb_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + const byte *buf = buf_in; + + /* TODO implement non-blocking + // wait to be able to write the first character + if (!uart_tx_wait(self, timeout)) { + *errcode = EAGAIN; + return MP_STREAM_ERROR; + } + */ + + // write the data + for (size_t i = 0; i < size; ++i) { + uart_tx_one_char(self->uart_id, *buf++); + } + + // return number of bytes written + return size; +} + +STATIC mp_uint_t pyb_uart_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { + pyb_uart_obj_t *self = self_in; + mp_uint_t ret; + if (request == MP_STREAM_POLL) { + mp_uint_t flags = arg; + ret = 0; + if ((flags & MP_STREAM_POLL_RD) && uart_rx_any(self->uart_id)) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR) && uart_tx_any_room(self->uart_id)) { + ret |= MP_STREAM_POLL_WR; + } + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +STATIC const mp_stream_p_t uart_stream_p = { + .read = pyb_uart_read, + .write = pyb_uart_write, + .ioctl = pyb_uart_ioctl, + .is_text = false, +}; + +const mp_obj_type_t pyb_uart_type = { + { &mp_type_type }, + .name = MP_QSTR_UART, + .print = pyb_uart_print, + .make_new = pyb_uart_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &uart_stream_p, + .locals_dict = (mp_obj_dict_t *)&pyb_uart_locals_dict, +}; diff --git a/ports/esp8266/machine_wdt.c b/ports/esp8266/machine_wdt.c new file mode 100644 index 0000000000..b64830e7d6 --- /dev/null +++ b/ports/esp8266/machine_wdt.c @@ -0,0 +1,85 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "user_interface.h" +#include "etshal.h" +#include "ets_alt_task.h" + +const mp_obj_type_t esp_wdt_type; + +typedef struct _machine_wdt_obj_t { + mp_obj_base_t base; +} machine_wdt_obj_t; + +STATIC machine_wdt_obj_t wdt_default = {{&esp_wdt_type}}; + +STATIC mp_obj_t machine_wdt_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + + mp_int_t id = 0; + if (n_args > 0) { + id = mp_obj_get_int(args[0]); + } + + switch (id) { + case 0: + ets_loop_dont_feed_sw_wdt = 1; + system_soft_wdt_feed(); + return &wdt_default; + default: + mp_raise_ValueError(NULL); + } +} + +STATIC mp_obj_t machine_wdt_feed(mp_obj_t self_in) { + (void)self_in; + system_soft_wdt_feed(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_wdt_feed_obj, machine_wdt_feed); + +STATIC mp_obj_t machine_wdt_deinit(mp_obj_t self_in) { + (void)self_in; + ets_wdt_disable(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_wdt_deinit_obj, machine_wdt_deinit); + +STATIC const mp_rom_map_elem_t machine_wdt_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_feed), MP_ROM_PTR(&machine_wdt_feed_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_wdt_deinit_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_wdt_locals_dict, machine_wdt_locals_dict_table); + +const mp_obj_type_t esp_wdt_type = { + { &mp_type_type }, + .name = MP_QSTR_WDT, + .make_new = machine_wdt_make_new, + .locals_dict = (mp_obj_dict_t *)&machine_wdt_locals_dict, +}; diff --git a/ports/esp8266/main.c b/ports/esp8266/main.c new file mode 100644 index 0000000000..5d7debced0 --- /dev/null +++ b/ports/esp8266/main.c @@ -0,0 +1,177 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * Copyright (c) 2015-2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/compile.h" +#include "py/runtime.h" +#include "py/stackctrl.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/gc.h" + +// This needs to be defined before any ESP SDK headers are included +#define USE_US_TIMER 1 + +#include "extmod/misc.h" +#include "shared/readline/readline.h" +#include "shared/runtime/pyexec.h" +#include "gccollect.h" +#include "user_interface.h" + +STATIC char heap[38 * 1024]; + +STATIC void mp_reset(void) { + mp_stack_set_top((void *)0x40000000); + mp_stack_set_limit(8192); + mp_hal_init(); + gc_init(heap, heap + sizeof(heap)); + mp_init(); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_lib)); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_)); + #if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA + extern void esp_native_code_init(void); + esp_native_code_init(); + #endif + pin_init0(); + readline_init0(); + dupterm_task_init(); + + // Activate UART(0) on dupterm slot 1 for the REPL + { + mp_obj_t args[2]; + args[0] = MP_OBJ_NEW_SMALL_INT(0); + args[1] = MP_OBJ_NEW_SMALL_INT(115200); + args[0] = pyb_uart_type.make_new(&pyb_uart_type, 2, 0, args); + args[1] = MP_OBJ_NEW_SMALL_INT(1); + extern mp_obj_t os_dupterm(size_t n_args, const mp_obj_t *args); + os_dupterm(2, args); + } + + #if MICROPY_MODULE_FROZEN + pyexec_frozen_module("_boot.py"); + pyexec_file_if_exists("boot.py"); + if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { + pyexec_file_if_exists("main.py"); + } + #endif +} + +void soft_reset(void) { + gc_sweep_all(); + mp_hal_stdout_tx_str("MPY: soft reboot\r\n"); + mp_hal_delay_us(10000); // allow UART to flush output + mp_reset(); + #if MICROPY_REPL_EVENT_DRIVEN + pyexec_event_repl_init(); + #endif +} + +void init_done(void) { + // Configure sleep, and put the radio to sleep if no interfaces are active + wifi_fpm_set_sleep_type(MODEM_SLEEP_T); + if (wifi_get_opmode() == NULL_MODE) { + wifi_fpm_open(); + wifi_fpm_do_sleep(0xfffffff); + } + + #if MICROPY_REPL_EVENT_DRIVEN + uart_task_init(); + #endif + mp_reset(); + mp_hal_stdout_tx_str("\r\n"); + #if MICROPY_REPL_EVENT_DRIVEN + pyexec_event_repl_init(); + #endif + + #if !MICROPY_REPL_EVENT_DRIVEN +soft_reset: + for (;;) { + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + if (pyexec_raw_repl() != 0) { + break; + } + } else { + if (pyexec_friendly_repl() != 0) { + break; + } + } + } + soft_reset(); + goto soft_reset; + #endif +} + +void user_init(void) { + system_timer_reinit(); + system_init_done_cb(init_done); +} + +#if !MICROPY_VFS +mp_lexer_t *mp_lexer_new_from_file(const char *filename) { + mp_raise_OSError(MP_ENOENT); +} + +mp_import_stat_t mp_import_stat(const char *path) { + (void)path; + return MP_IMPORT_STAT_NO_EXIST; +} + +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); + +#endif + +void MP_FASTCODE(nlr_jump_fail)(void *val) { + printf("NLR jump failed\n"); + for (;;) { + } +} + +// void __assert(const char *file, int line, const char *func, const char *expr) { +void __assert(const char *file, int line, const char *expr) { + printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); + for (;;) { + } +} + +#if !MICROPY_DEBUG_PRINTERS +// With MICROPY_DEBUG_PRINTERS disabled DEBUG_printf is not defined but it +// is still needed by esp-open-lwip for debugging output, so define it here. +#include +int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args); +int DEBUG_printf(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = mp_vprintf(MICROPY_DEBUG_PRINTER, fmt, ap); + va_end(ap); + return ret; +} +#endif diff --git a/ports/esp8266/makeimg.py b/ports/esp8266/makeimg.py new file mode 100644 index 0000000000..4d31cc0799 --- /dev/null +++ b/ports/esp8266/makeimg.py @@ -0,0 +1,54 @@ +import sys +import struct +import hashlib + +# This region at the start of flash contains a small header and then segments +# containing .text, .data and .rodata, and so must be large enough to hold all +# of this. This data is loaded to the appropriate places in RAM by the ROM +# bootloader at boot. After this in flash comes .irom0.text, which must begin +# on a flash erase-page boundary. +SEGS_MAX_SIZE = 0x9000 + +assert len(sys.argv) == 4 + +md5 = hashlib.md5() + +with open(sys.argv[3], "wb") as fout: + + with open(sys.argv[1], "rb") as f: + data_flash = f.read() + fout.write(data_flash) + # First 4 bytes include flash size, etc. which may be changed + # by esptool.py, etc. + md5.update(data_flash[4:]) + print("flash ", len(data_flash)) + + # Print info about segments in this first part of flash + num_segs = struct.unpack_from("= 4 + fout.write(pad[:-4]) + md5.update(pad[:-4]) + len_data = struct.pack("I", SEGS_MAX_SIZE + len(data_rom)) + fout.write(len_data) + md5.update(len_data) + print("padding ", len(pad)) + + fout.write(data_rom) + md5.update(data_rom) + print("irom0text", len(data_rom)) + + fout.write(md5.digest()) + + print("total ", SEGS_MAX_SIZE + len(data_rom)) + print("md5 ", md5.hexdigest()) diff --git a/ports/esp8266/modesp.c b/ports/esp8266/modesp.c new file mode 100644 index 0000000000..eb79d20bc5 --- /dev/null +++ b/ports/esp8266/modesp.c @@ -0,0 +1,382 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/gc.h" +#include "py/runtime.h" +#include "py/persistentcode.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "drivers/dht/dht.h" +#include "uart.h" +#include "user_interface.h" +#include "mem.h" +#include "ets_alt_task.h" +#include "espapa102.h" +#include "modmachine.h" + +#define MODESP_INCLUDE_CONSTANTS (1) + +void error_check(bool status, mp_rom_error_text_t msg) { + if (!status) { + mp_raise_msg(&mp_type_OSError, msg); + } +} + +STATIC mp_obj_t esp_osdebug(mp_obj_t val) { + if (val == mp_const_none) { + uart_os_config(-1); + } else { + uart_os_config(mp_obj_get_int(val)); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_osdebug_obj, esp_osdebug); + +STATIC mp_obj_t esp_sleep_type(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return mp_obj_new_int(wifi_get_sleep_type()); + } else { + wifi_set_sleep_type(mp_obj_get_int(args[0])); + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_sleep_type_obj, 0, 1, esp_sleep_type); + +STATIC mp_obj_t esp_deepsleep(size_t n_args, const mp_obj_t *args) { + uint32_t sleep_us = n_args > 0 ? mp_obj_get_int(args[0]) : 0; + // prepare for RTC reset at wake up + rtc_prepare_deepsleep(sleep_us); + system_deep_sleep_set_option(n_args > 1 ? mp_obj_get_int(args[1]) : 0); + system_deep_sleep(sleep_us); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_deepsleep_obj, 0, 2, esp_deepsleep); + +STATIC mp_obj_t esp_flash_id() { + return mp_obj_new_int(spi_flash_get_id()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_id_obj, esp_flash_id); + +STATIC mp_obj_t esp_flash_read(mp_obj_t offset_in, mp_obj_t len_or_buf_in) { + mp_int_t offset = mp_obj_get_int(offset_in); + + mp_int_t len; + byte *buf; + bool alloc_buf = mp_obj_is_int(len_or_buf_in); + + if (alloc_buf) { + len = mp_obj_get_int(len_or_buf_in); + buf = m_new(byte, len); + } else { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(len_or_buf_in, &bufinfo, MP_BUFFER_WRITE); + len = bufinfo.len; + buf = bufinfo.buf; + } + + // We know that allocation will be 4-byte aligned for sure + SpiFlashOpResult res = spi_flash_read(offset, (uint32_t *)buf, len); + if (res == SPI_FLASH_RESULT_OK) { + if (alloc_buf) { + return mp_obj_new_bytes(buf, len); + } + return mp_const_none; + } + if (alloc_buf) { + m_del(byte, buf, len); + } + mp_raise_OSError(res == SPI_FLASH_RESULT_TIMEOUT ? MP_ETIMEDOUT : MP_EIO); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp_flash_read_obj, esp_flash_read); + +STATIC mp_obj_t esp_flash_write(mp_obj_t offset_in, const mp_obj_t buf_in) { + mp_int_t offset = mp_obj_get_int(offset_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len & 0x3) { + mp_raise_ValueError(MP_ERROR_TEXT("len must be multiple of 4")); + } + ets_loop_iter(); // flash access takes time so run any pending tasks + SpiFlashOpResult res = spi_flash_write(offset, bufinfo.buf, bufinfo.len); + ets_loop_iter(); + if (res == SPI_FLASH_RESULT_OK) { + return mp_const_none; + } + mp_raise_OSError(res == SPI_FLASH_RESULT_TIMEOUT ? MP_ETIMEDOUT : MP_EIO); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp_flash_write_obj, esp_flash_write); + +STATIC mp_obj_t esp_flash_erase(mp_obj_t sector_in) { + mp_int_t sector = mp_obj_get_int(sector_in); + ets_loop_iter(); // flash access takes time so run any pending tasks + SpiFlashOpResult res = spi_flash_erase_sector(sector); + ets_loop_iter(); + if (res == SPI_FLASH_RESULT_OK) { + return mp_const_none; + } + mp_raise_OSError(res == SPI_FLASH_RESULT_TIMEOUT ? MP_ETIMEDOUT : MP_EIO); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_flash_erase_obj, esp_flash_erase); + +STATIC mp_obj_t esp_flash_size(void) { + extern char flashchip; + // For SDK 1.5.2, either address has shifted and not mirrored in + // eagle.rom.addr.v6.ld, or extra initial member was added. + SpiFlashChip *flash = (SpiFlashChip *)(&flashchip + 4); + #if 0 + printf("deviceId: %x\n", flash->deviceId); + printf("chip_size: %u\n", flash->chip_size); + printf("block_size: %u\n", flash->block_size); + printf("sector_size: %u\n", flash->sector_size); + printf("page_size: %u\n", flash->page_size); + printf("status_mask: %u\n", flash->status_mask); + #endif + return mp_obj_new_int_from_uint(flash->chip_size); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_size_obj, esp_flash_size); + +// If there's just 1 loadable segment at the start of flash, +// we assume there's a yaota8266 bootloader. +#define IS_OTA_FIRMWARE() ((*(uint32_t *)0x40200000 & 0xff00) == 0x100) + +extern byte _firmware_size[]; + +STATIC mp_obj_t esp_flash_user_start(void) { + return MP_OBJ_NEW_SMALL_INT((uint32_t)_firmware_size); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_user_start_obj, esp_flash_user_start); + +STATIC mp_obj_t esp_check_fw(void) { + MD5_CTX ctx; + char *fw_start = (char *)0x40200000; + if (IS_OTA_FIRMWARE()) { + // Skip yaota8266 bootloader + fw_start += 0x3c000; + } + + uint32_t size = *(uint32_t *)(fw_start + 0x8ffc); + printf("size: %d\n", size); + if (size > 1024 * 1024) { + printf("Invalid size\n"); + return mp_const_false; + } + MD5Init(&ctx); + MD5Update(&ctx, fw_start + 4, size - 4); + unsigned char digest[16]; + MD5Final(digest, &ctx); + printf("md5: "); + for (int i = 0; i < 16; i++) { + printf("%02x", digest[i]); + } + printf("\n"); + return mp_obj_new_bool(memcmp(digest, fw_start + size, sizeof(digest)) == 0); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_check_fw_obj, esp_check_fw); + +#if MICROPY_ESP8266_APA102 +STATIC mp_obj_t esp_apa102_write_(mp_obj_t clockPin, mp_obj_t dataPin, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + esp_apa102_write(mp_obj_get_pin_obj(clockPin)->phys_port, + mp_obj_get_pin_obj(dataPin)->phys_port, + (uint8_t *)bufinfo.buf, bufinfo.len); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp_apa102_write_obj, esp_apa102_write_); +#endif + +STATIC mp_obj_t esp_freemem() { + return MP_OBJ_NEW_SMALL_INT(system_get_free_heap_size()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_freemem_obj, esp_freemem); + +STATIC mp_obj_t esp_meminfo() { + system_print_meminfo(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_meminfo_obj, esp_meminfo); + +STATIC mp_obj_t esp_malloc(mp_obj_t size_in) { + return MP_OBJ_NEW_SMALL_INT((mp_uint_t)os_malloc(mp_obj_get_int(size_in))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_malloc_obj, esp_malloc); + +STATIC mp_obj_t esp_free(mp_obj_t addr_in) { + os_free((void *)mp_obj_get_int(addr_in)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_free_obj, esp_free); + +STATIC mp_obj_t esp_esf_free_bufs(mp_obj_t idx_in) { + return MP_OBJ_NEW_SMALL_INT(ets_esf_free_bufs(mp_obj_get_int(idx_in))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_esf_free_bufs_obj, esp_esf_free_bufs); + +#if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA + +// We provide here a way of committing executable data to a region from +// which it can be executed by the CPU. There are 2 such writable regions: +// - iram1, which may have some space left at the end of it +// - memory-mapped flash rom +// +// By default the iram1 region (the space at the end of it) is used. The +// user can select iram1 or a section of flash by calling the +// esp.set_native_code_location() function; see below. If flash is selected +// then it is erased as needed. + +#define IRAM1_END (0x40108000) +#define FLASH_START (0x40200000) +#define FLASH_END (0x40300000) +#define FLASH_SEC_SIZE (4096) + +#define ESP_NATIVE_CODE_IRAM1 (0) +#define ESP_NATIVE_CODE_FLASH (1) + +extern uint32_t _lit4_end; +STATIC uint32_t esp_native_code_location; +STATIC uint32_t esp_native_code_start; +STATIC uint32_t esp_native_code_end; +STATIC uint32_t esp_native_code_cur; +STATIC uint32_t esp_native_code_erased; + +void esp_native_code_init(void) { + esp_native_code_location = ESP_NATIVE_CODE_IRAM1; + esp_native_code_start = (uint32_t)&_lit4_end; + esp_native_code_end = IRAM1_END; + esp_native_code_cur = esp_native_code_start; + esp_native_code_erased = 0; +} + +void *esp_native_code_commit(void *buf, size_t len, void *reloc) { + // printf("COMMIT(buf=%p, len=%u, start=%08x, cur=%08x, end=%08x, erased=%08x)\n", buf, len, esp_native_code_start, esp_native_code_cur, esp_native_code_end, esp_native_code_erased); + + len = (len + 3) & ~3; + if (esp_native_code_cur + len > esp_native_code_end) { + mp_raise_msg_varg(&mp_type_MemoryError, + MP_ERROR_TEXT("memory allocation failed, allocating %u bytes for native code"), (uint)len); + } + + void *dest; + if (esp_native_code_location == ESP_NATIVE_CODE_IRAM1) { + dest = (void *)esp_native_code_cur; + } else { + dest = (void *)(FLASH_START + esp_native_code_cur); + } + if (reloc) { + mp_native_relocate(reloc, buf, (uintptr_t)dest); + } + + if (esp_native_code_location == ESP_NATIVE_CODE_IRAM1) { + memcpy(dest, buf, len); + } else { + SpiFlashOpResult res; + while (esp_native_code_erased < esp_native_code_cur + len) { + ets_loop_iter(); // flash access takes time so run any pending tasks + res = spi_flash_erase_sector(esp_native_code_erased / FLASH_SEC_SIZE); + if (res != SPI_FLASH_RESULT_OK) { + break; + } + esp_native_code_erased += FLASH_SEC_SIZE; + } + ets_loop_iter(); + if (res == SPI_FLASH_RESULT_OK) { + res = spi_flash_write(esp_native_code_cur, buf, len); + ets_loop_iter(); + } + if (res != SPI_FLASH_RESULT_OK) { + mp_raise_OSError(res == SPI_FLASH_RESULT_TIMEOUT ? MP_ETIMEDOUT : MP_EIO); + } + } + + esp_native_code_cur += len; + + return dest; +} + +STATIC mp_obj_t esp_set_native_code_location(mp_obj_t start_in, mp_obj_t len_in) { + if (start_in == mp_const_none && len_in == mp_const_none) { + // use end of iram1 region + esp_native_code_init(); + } else { + // use flash; input params are byte offsets from start of flash + esp_native_code_location = ESP_NATIVE_CODE_FLASH; + esp_native_code_start = mp_obj_get_int(start_in); + esp_native_code_end = esp_native_code_start + mp_obj_get_int(len_in); + esp_native_code_cur = esp_native_code_start; + esp_native_code_erased = esp_native_code_start; + // memory-mapped flash is limited in extents to 1MByte + if (esp_native_code_end > FLASH_END - FLASH_START) { + mp_raise_ValueError(MP_ERROR_TEXT("flash location must be below 1MByte")); + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp_set_native_code_location_obj, esp_set_native_code_location); + +#endif + +STATIC const mp_rom_map_elem_t esp_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp) }, + + { MP_ROM_QSTR(MP_QSTR_osdebug), MP_ROM_PTR(&esp_osdebug_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_type), MP_ROM_PTR(&esp_sleep_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_deepsleep), MP_ROM_PTR(&esp_deepsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_id), MP_ROM_PTR(&esp_flash_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_read), MP_ROM_PTR(&esp_flash_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_write), MP_ROM_PTR(&esp_flash_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_erase), MP_ROM_PTR(&esp_flash_erase_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_size), MP_ROM_PTR(&esp_flash_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_user_start), MP_ROM_PTR(&esp_flash_user_start_obj) }, + #if MICROPY_ESP8266_APA102 + { MP_ROM_QSTR(MP_QSTR_apa102_write), MP_ROM_PTR(&esp_apa102_write_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dht_readinto), MP_ROM_PTR(&dht_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_freemem), MP_ROM_PTR(&esp_freemem_obj) }, + { MP_ROM_QSTR(MP_QSTR_meminfo), MP_ROM_PTR(&esp_meminfo_obj) }, + { MP_ROM_QSTR(MP_QSTR_check_fw), MP_ROM_PTR(&esp_check_fw_obj) }, + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&pyb_info_obj) }, // TODO delete/rename/move elsewhere + { MP_ROM_QSTR(MP_QSTR_malloc), MP_ROM_PTR(&esp_malloc_obj) }, + { MP_ROM_QSTR(MP_QSTR_free), MP_ROM_PTR(&esp_free_obj) }, + { MP_ROM_QSTR(MP_QSTR_esf_free_bufs), MP_ROM_PTR(&esp_esf_free_bufs_obj) }, + #if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA + { MP_ROM_QSTR(MP_QSTR_set_native_code_location), MP_ROM_PTR(&esp_set_native_code_location_obj) }, + #endif + + #if MODESP_INCLUDE_CONSTANTS + { MP_ROM_QSTR(MP_QSTR_SLEEP_NONE), MP_ROM_INT(NONE_SLEEP_T) }, + { MP_ROM_QSTR(MP_QSTR_SLEEP_LIGHT), MP_ROM_INT(LIGHT_SLEEP_T) }, + { MP_ROM_QSTR(MP_QSTR_SLEEP_MODEM), MP_ROM_INT(MODEM_SLEEP_T) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(esp_module_globals, esp_module_globals_table); + +const mp_obj_module_t esp_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&esp_module_globals, +}; diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c new file mode 100644 index 0000000000..39a890f56f --- /dev/null +++ b/ports/esp8266/modmachine.c @@ -0,0 +1,457 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2015 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "shared/runtime/pyexec.h" + +// This needs to be set before we include the RTOS headers +#define USE_US_TIMER 1 + +#include "extmod/machine_bitstream.h" +#include "extmod/machine_mem.h" +#include "extmod/machine_signal.h" +#include "extmod/machine_pulse.h" +#include "extmod/machine_pwm.h" +#include "extmod/machine_i2c.h" +#include "extmod/machine_spi.h" +#include "modmachine.h" + +#include "xtirq.h" +#include "os_type.h" +#include "osapi.h" +#include "etshal.h" +#include "ets_alt_task.h" +#include "user_interface.h" + +#if MICROPY_PY_MACHINE + +// #define MACHINE_WAKE_IDLE (0x01) +// #define MACHINE_WAKE_SLEEP (0x02) +#define MACHINE_WAKE_DEEPSLEEP (0x04) + +extern const mp_obj_type_t esp_wdt_type; + +STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + // get + return mp_obj_new_int(system_get_cpu_freq() * 1000000); + } else { + // set + mp_int_t freq = mp_obj_get_int(args[0]) / 1000000; + if (freq != 80 && freq != 160) { + mp_raise_ValueError(MP_ERROR_TEXT("frequency can only be either 80Mhz or 160MHz")); + } + system_update_cpu_freq(freq); + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj, 0, 1, machine_freq); + +STATIC mp_obj_t machine_reset(void) { + system_restart(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset); + +STATIC mp_obj_t machine_soft_reset(void) { + pyexec_system_exit = PYEXEC_FORCED_EXIT; + mp_raise_type(&mp_type_SystemExit); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_soft_reset_obj, machine_soft_reset); + +STATIC mp_obj_t machine_reset_cause(void) { + return MP_OBJ_NEW_SMALL_INT(system_get_rst_info()->reason); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); + +STATIC mp_obj_t machine_unique_id(void) { + uint32_t id = system_get_chip_id(); + return mp_obj_new_bytes((byte *)&id, sizeof(id)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); + +STATIC mp_obj_t machine_idle(void) { + uint32_t t = mp_hal_ticks_cpu(); + asm ("waiti 0"); + t = mp_hal_ticks_cpu() - t; + ets_event_poll(); // handle any events after possibly a long wait (eg feed WDT) + return MP_OBJ_NEW_SMALL_INT(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); + +STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { + uint32_t max_us = 0xffffffff; + if (n_args == 1) { + mp_int_t max_ms = mp_obj_get_int(args[0]); + if (max_ms < 0) { + max_ms = 0; + } + max_us = max_ms * 1000; + } + uint32_t wifi_mode = wifi_get_opmode(); + uint32_t start = system_get_time(); + while (system_get_time() - start <= max_us) { + ets_event_poll(); + if (wifi_mode == NULL_MODE) { + // Can only idle if the wifi is off + asm ("waiti 0"); + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine_lightsleep); + +STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { + // default to sleep forever + uint32_t sleep_us = 0; + + // see if RTC.ALARM0 should wake the device + if (pyb_rtc_alarm0_wake & MACHINE_WAKE_DEEPSLEEP) { + uint64_t t = pyb_rtc_get_us_since_epoch(); + if (pyb_rtc_alarm0_expiry <= t) { + sleep_us = 1; // alarm already expired so wake immediately + } else { + uint64_t delta = pyb_rtc_alarm0_expiry - t; + if (delta <= 0xffffffff) { + // sleep for the desired time + sleep_us = delta; + } else { + // overflow, just set to maximum sleep time + sleep_us = 0xffffffff; + } + } + } + + // if an argument is given then that's the maximum time to sleep for + if (n_args == 1) { + mp_int_t max_ms = mp_obj_get_int(args[0]); + if (max_ms <= 0) { + max_ms = 1; + } + uint32_t max_us = max_ms * 1000; + if (sleep_us == 0 || max_us < sleep_us) { + sleep_us = max_us; + } + } + + // prepare for RTC reset at wake up + rtc_prepare_deepsleep(sleep_us); + // put the device in a deep-sleep state + system_deep_sleep_set_option(0); // default power down mode; TODO check this + system_deep_sleep(sleep_us); + + for (;;) { + // we must not return + ets_loop_iter(); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj, 0, 1, machine_deepsleep); + +// These values are from the datasheet +#define ESP_TIMER_US_MIN (100) +#define ESP_TIMER_US_MAX (0xfffffff) +#define ESP_TIMER_MS_MAX (0x689d0) + +typedef struct _esp_timer_obj_t { + mp_obj_base_t base; + os_timer_t timer; + uint32_t remain_ms; // if non-zero, remaining time to handle large periods + uint32_t period_ms; // if non-zero, periodic timer with a large period + mp_obj_t callback; +} esp_timer_obj_t; + +STATIC void esp_timer_arm_ms(esp_timer_obj_t *self, uint32_t ms, bool repeat) { + if (ms <= ESP_TIMER_MS_MAX) { + self->remain_ms = 0; + self->period_ms = 0; + } else { + self->remain_ms = ms - ESP_TIMER_MS_MAX; + if (repeat) { + repeat = false; + self->period_ms = ms; + } else { + self->period_ms = 0; + } + ms = ESP_TIMER_MS_MAX; + } + os_timer_arm(&self->timer, ms, repeat); +} + +STATIC void esp_timer_arm_us(esp_timer_obj_t *self, uint32_t us, bool repeat) { + if (us < ESP_TIMER_US_MIN) { + us = ESP_TIMER_US_MIN; + } + if (us <= ESP_TIMER_US_MAX) { + self->remain_ms = 0; + self->period_ms = 0; + os_timer_arm_us(&self->timer, us, repeat); + } else { + esp_timer_arm_ms(self, us / 1000, repeat); + } +} + +const mp_obj_type_t esp_timer_type; + +STATIC void esp_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + esp_timer_obj_t *self = self_in; + mp_printf(print, "Timer(%p)", &self->timer); +} + +STATIC mp_obj_t esp_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + esp_timer_obj_t *tim = m_new_obj(esp_timer_obj_t); + tim->base.type = &esp_timer_type; + return tim; +} + +STATIC void esp_timer_cb(void *arg) { + esp_timer_obj_t *self = arg; + if (self->remain_ms != 0) { + // Handle periods larger than the maximum system period + uint32_t next_period_ms = self->remain_ms; + if (next_period_ms > ESP_TIMER_MS_MAX) { + next_period_ms = ESP_TIMER_MS_MAX; + } + self->remain_ms -= next_period_ms; + os_timer_arm(&self->timer, next_period_ms, false); + } else { + mp_sched_schedule(self->callback, self); + if (self->period_ms != 0) { + // A periodic timer with a larger period: reschedule it + esp_timer_arm_ms(self, self->period_ms, true); + } + } +} + +STATIC mp_obj_t esp_timer_init_helper(esp_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_mode, + ARG_callback, + ARG_period, + ARG_tick_hz, + ARG_freq, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + { MP_QSTR_tick_hz, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, + #if MICROPY_PY_BUILTINS_FLOAT + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #else + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + #endif + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self->callback = args[ARG_callback].u_obj; + // Be sure to disarm timer before making any changes + os_timer_disarm(&self->timer); + os_timer_setfn(&self->timer, esp_timer_cb, self); + + #if MICROPY_PY_BUILTINS_FLOAT + if (args[ARG_freq].u_obj != mp_const_none) { + mp_float_t freq = mp_obj_get_float(args[ARG_freq].u_obj); + if (freq < 0.001) { + esp_timer_arm_ms(self, (mp_int_t)(1000 / freq), args[ARG_mode].u_int); + } else { + esp_timer_arm_us(self, (mp_int_t)(1000000 / freq), args[ARG_mode].u_int); + } + } + #else + if (args[ARG_freq].u_int != 0xffffffff) { + esp_timer_arm_us(self, 1000000 / args[ARG_freq].u_int, args[ARG_mode].u_int); + } + #endif + else { + mp_int_t period = args[ARG_period].u_int; + mp_int_t hz = args[ARG_tick_hz].u_int; + if (hz == 1000) { + esp_timer_arm_ms(self, period, args[ARG_mode].u_int); + } else if (hz == 1000000) { + esp_timer_arm_us(self, period, args[ARG_mode].u_int); + } else { + // Use a long long to ensure that we don't either overflow or loose accuracy + uint64_t period_us = (((uint64_t)period) * 1000000) / hz; + if (period_us < 0x80000000ull) { + esp_timer_arm_us(self, (mp_int_t)period_us, args[ARG_mode].u_int); + } else { + esp_timer_arm_ms(self, (mp_int_t)(period_us / 1000), args[ARG_mode].u_int); + } + } + } + + return mp_const_none; +} + +STATIC mp_obj_t esp_timer_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return esp_timer_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp_timer_init_obj, 1, esp_timer_init); + +STATIC mp_obj_t esp_timer_deinit(mp_obj_t self_in) { + esp_timer_obj_t *self = self_in; + os_timer_disarm(&self->timer); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_timer_deinit_obj, esp_timer_deinit); + +STATIC const mp_rom_map_elem_t esp_timer_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp_timer_init_obj) }, +// { MP_ROM_QSTR(MP_QSTR_callback), MP_ROM_PTR(&esp_timer_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_ONE_SHOT), MP_ROM_INT(false) }, + { MP_ROM_QSTR(MP_QSTR_PERIODIC), MP_ROM_INT(true) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp_timer_locals_dict, esp_timer_locals_dict_table); + +const mp_obj_type_t esp_timer_type = { + { &mp_type_type }, + .name = MP_QSTR_Timer, + .print = esp_timer_print, + .make_new = esp_timer_make_new, + .locals_dict = (mp_obj_dict_t *)&esp_timer_locals_dict, +}; + +// this bit is unused in the Xtensa PS register +#define ETS_LOOP_ITER_BIT (12) + +STATIC mp_obj_t machine_disable_irq(void) { + uint32_t state = disable_irq(); + state = (state & ~(1 << ETS_LOOP_ITER_BIT)) | (ets_loop_iter_disable << ETS_LOOP_ITER_BIT); + ets_loop_iter_disable = 1; + return mp_obj_new_int(state); +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); + +STATIC mp_obj_t machine_enable_irq(mp_obj_t state_in) { + uint32_t state = mp_obj_get_int(state_in); + ets_loop_iter_disable = (state >> ETS_LOOP_ITER_BIT) & 1; + enable_irq(state & ~(1 << ETS_LOOP_ITER_BIT)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_enable_irq_obj, machine_enable_irq); + +// Custom version of this function that feeds system WDT if necessary +mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us) { + int nchanges = 2; + uint32_t start = system_get_time(); // in microseconds + for (;;) { + uint32_t dt = system_get_time() - start; + + // Check if pin changed to wanted value + if (mp_hal_pin_read(pin) == pulse_level) { + if (--nchanges == 0) { + return dt; + } + pulse_level = 1 - pulse_level; + start = system_get_time(); + continue; + } + + // Check for timeout + if (dt >= timeout_us) { + return (mp_uint_t)-nchanges; + } + + // Only feed WDT every now and then, to make sure edge timing is accurate + if ((dt & 0xffff) == 0xffff && !ets_loop_dont_feed_sw_wdt) { + system_soft_wdt_feed(); + } + } +} + +STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, + { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + + { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_soft_reset), MP_ROM_PTR(&machine_soft_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, + { MP_ROM_QSTR(MP_QSTR_unique_id), MP_ROM_PTR(&machine_unique_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_lightsleep), MP_ROM_PTR(&machine_lightsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_deepsleep), MP_ROM_PTR(&machine_deepsleep_obj) }, + + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, + + #if MICROPY_PY_MACHINE_BITSTREAM + { MP_ROM_QSTR(MP_QSTR_bitstream), MP_ROM_PTR(&machine_bitstream_obj) }, + #endif + + { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + + { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&pyb_rtc_type) }, + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&esp_timer_type) }, + { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&esp_wdt_type) }, + { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pyb_pin_type) }, + { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, + { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, + { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, + #if MICROPY_PY_MACHINE_I2C + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + #endif + #if MICROPY_PY_MACHINE_SPI + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hspi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, + #endif + + // wake abilities + { MP_ROM_QSTR(MP_QSTR_DEEPSLEEP), MP_ROM_INT(MACHINE_WAKE_DEEPSLEEP) }, + + // reset causes + { MP_ROM_QSTR(MP_QSTR_PWRON_RESET), MP_ROM_INT(REASON_DEFAULT_RST) }, + { MP_ROM_QSTR(MP_QSTR_HARD_RESET), MP_ROM_INT(REASON_EXT_SYS_RST) }, + { MP_ROM_QSTR(MP_QSTR_DEEPSLEEP_RESET), MP_ROM_INT(REASON_DEEP_SLEEP_AWAKE) }, + { MP_ROM_QSTR(MP_QSTR_WDT_RESET), MP_ROM_INT(REASON_WDT_RST) }, + { MP_ROM_QSTR(MP_QSTR_SOFT_RESET), MP_ROM_INT(REASON_SOFT_RESTART) }, +}; + +STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); + +const mp_obj_module_t mp_module_machine = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&machine_module_globals, +}; + +#endif // MICROPY_PY_MACHINE diff --git a/ports/esp8266/modmachine.h b/ports/esp8266/modmachine.h new file mode 100644 index 0000000000..4a73d3b8e8 --- /dev/null +++ b/ports/esp8266/modmachine.h @@ -0,0 +1,38 @@ +#ifndef MICROPY_INCLUDED_ESP8266_MODMACHINE_H +#define MICROPY_INCLUDED_ESP8266_MODMACHINE_H + +#include "py/obj.h" + +extern const mp_obj_type_t pyb_pin_type; +extern const mp_obj_type_t machine_adc_type; +extern const mp_obj_type_t pyb_rtc_type; +extern const mp_obj_type_t pyb_uart_type; +extern const mp_obj_type_t pyb_i2c_type; +extern const mp_obj_type_t machine_hspi_type; + +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_info_obj); + +typedef struct _pyb_pin_obj_t { + mp_obj_base_t base; + uint16_t phys_port; + uint16_t func; + uint32_t periph; +} pyb_pin_obj_t; + +const pyb_pin_obj_t pyb_pin_obj[16 + 1]; + +void pin_init0(void); + +uint mp_obj_get_pin(mp_obj_t pin_in); +pyb_pin_obj_t *mp_obj_get_pin_obj(mp_obj_t pin_in); +int pin_get(uint pin); +void pin_set(uint pin, int value); + +extern uint32_t pyb_rtc_alarm0_wake; +extern uint64_t pyb_rtc_alarm0_expiry; + +void pyb_rtc_set_us_since_epoch(uint64_t nowus); +uint64_t pyb_rtc_get_us_since_epoch(); +void rtc_prepare_deepsleep(uint64_t sleep_us); + +#endif // MICROPY_INCLUDED_ESP8266_MODMACHINE_H diff --git a/ports/esp8266/modnetwork.c b/ports/esp8266/modnetwork.c new file mode 100644 index 0000000000..6988e09bd1 --- /dev/null +++ b/ports/esp8266/modnetwork.c @@ -0,0 +1,547 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "py/objlist.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "shared/netutils/netutils.h" +#include "queue.h" +#include "user_interface.h" +#include "espconn.h" +#include "spi_flash.h" +#include "ets_alt_task.h" +#include "lwip/dns.h" + +#define MODNETWORK_INCLUDE_CONSTANTS (1) + +typedef struct _wlan_if_obj_t { + mp_obj_base_t base; + int if_id; +} wlan_if_obj_t; + +void error_check(bool status, const char *msg); +const mp_obj_type_t wlan_if_type; + +STATIC const wlan_if_obj_t wlan_objs[] = { + {{&wlan_if_type}, STATION_IF}, + {{&wlan_if_type}, SOFTAP_IF}, +}; + +STATIC void require_if(mp_obj_t wlan_if, int if_no) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(wlan_if); + if (self->if_id != if_no) { + error_check(false, if_no == STATION_IF ? "STA required" : "AP required"); + } +} + +STATIC mp_obj_t get_wlan(size_t n_args, const mp_obj_t *args) { + int idx = 0; + if (n_args > 0) { + idx = mp_obj_get_int(args[0]); + if (idx < 0 || idx >= sizeof(wlan_objs)) { + mp_raise_ValueError(NULL); + } + } + return MP_OBJ_FROM_PTR(&wlan_objs[idx]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(get_wlan_obj, 0, 1, get_wlan); + +STATIC mp_obj_t esp_active(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t mode = wifi_get_opmode(); + if (n_args > 1) { + int mask = self->if_id == STATION_IF ? STATION_MODE : SOFTAP_MODE; + if (mp_obj_get_int(args[1]) != 0) { + mode |= mask; + } else { + mode &= ~mask; + } + if (mode != NULL_MODE) { + wifi_fpm_do_wakeup(); + wifi_fpm_close(); + } + error_check(wifi_set_opmode(mode), "Cannot update i/f status"); + if (mode == NULL_MODE) { + // Wait for the interfaces to go down before forcing power management + while (wifi_get_opmode() != NULL_MODE) { + ets_loop_iter(); + } + wifi_fpm_open(); + wifi_fpm_do_sleep(0xfffffff); + } + return mp_const_none; + } + + // Get active status + if (self->if_id == STATION_IF) { + return mp_obj_new_bool(mode & STATION_MODE); + } else { + return mp_obj_new_bool(mode & SOFTAP_MODE); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_active_obj, 1, 2, esp_active); + +STATIC mp_obj_t esp_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_ssid, ARG_password, ARG_bssid }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + require_if(pos_args[0], STATION_IF); + struct station_config config = {{0}}; + size_t len; + const char *p; + bool set_config = false; + + // set parameters based on given args + if (args[ARG_ssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_ssid].u_obj, &len); + len = MIN(len, sizeof(config.ssid)); + memcpy(config.ssid, p, len); + set_config = true; + } + if (args[ARG_password].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_password].u_obj, &len); + len = MIN(len, sizeof(config.password)); + memcpy(config.password, p, len); + set_config = true; + } + if (args[ARG_bssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_bssid].u_obj, &len); + if (len != sizeof(config.bssid)) { + mp_raise_ValueError(NULL); + } + config.bssid_set = 1; + memcpy(config.bssid, p, sizeof(config.bssid)); + set_config = true; + } + + if (set_config) { + error_check(wifi_station_set_config(&config), "Cannot set STA config"); + } + error_check(wifi_station_connect(), "Cannot connect to AP"); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp_connect_obj, 1, esp_connect); + +STATIC mp_obj_t esp_disconnect(mp_obj_t self_in) { + require_if(self_in, STATION_IF); + error_check(wifi_station_disconnect(), "Cannot disconnect from AP"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_disconnect_obj, esp_disconnect); + +STATIC mp_obj_t esp_status(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args == 1) { + // Get link status + if (self->if_id == STATION_IF) { + return MP_OBJ_NEW_SMALL_INT(wifi_station_get_connect_status()); + } + return MP_OBJ_NEW_SMALL_INT(-1); + } else { + // Get specific status parameter + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_rssi: + if (self->if_id == STATION_IF) { + return MP_OBJ_NEW_SMALL_INT(wifi_station_get_rssi()); + } + } + mp_raise_ValueError(MP_ERROR_TEXT("unknown status param")); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_status_obj, 1, 2, esp_status); + +STATIC mp_obj_t *esp_scan_list = NULL; + +STATIC void esp_scan_cb(void *result, STATUS status) { + if (esp_scan_list == NULL) { + // called unexpectedly + return; + } + if (result && status == 0) { + // we need to catch any memory errors + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + for (struct bss_info *bs = result; bs; bs = STAILQ_NEXT(bs, next)) { + mp_obj_tuple_t *t = mp_obj_new_tuple(6, NULL); + #if 1 + // struct bss_info::ssid_len is not documented in SDK API Guide, + // but is present in SDK headers since 1.4.0 + t->items[0] = mp_obj_new_bytes(bs->ssid, bs->ssid_len); + #else + t->items[0] = mp_obj_new_bytes(bs->ssid, strlen((char *)bs->ssid)); + #endif + t->items[1] = mp_obj_new_bytes(bs->bssid, sizeof(bs->bssid)); + t->items[2] = MP_OBJ_NEW_SMALL_INT(bs->channel); + t->items[3] = MP_OBJ_NEW_SMALL_INT(bs->rssi); + t->items[4] = MP_OBJ_NEW_SMALL_INT(bs->authmode); + t->items[5] = MP_OBJ_NEW_SMALL_INT(bs->is_hidden); + mp_obj_list_append(*esp_scan_list, MP_OBJ_FROM_PTR(t)); + } + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + // indicate error + *esp_scan_list = MP_OBJ_NULL; + } + } else { + // indicate error + *esp_scan_list = MP_OBJ_NULL; + } + esp_scan_list = NULL; +} + +STATIC mp_obj_t esp_scan(mp_obj_t self_in) { + require_if(self_in, STATION_IF); + if ((wifi_get_opmode() & STATION_MODE) == 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("STA must be active")); + } + mp_obj_t list = mp_obj_new_list(0, NULL); + esp_scan_list = &list; + struct scan_config config = {0}; + config.show_hidden = 1; + wifi_station_scan(&config, (scan_done_cb_t)esp_scan_cb); + while (esp_scan_list != NULL) { + // our esp_scan_cb is called via ets_loop_iter so it's safe to set the + // esp_scan_list variable to NULL without disabling interrupts + if (MP_STATE_THREAD(mp_pending_exception) != NULL) { + esp_scan_list = NULL; + mp_handle_pending(true); + } + ets_loop_iter(); + } + if (list == MP_OBJ_NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("scan failed")); + } + return list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_scan_obj, esp_scan); + +/// \method isconnected() +/// Return True if connected to an AP and an IP address has been assigned, +/// false otherwise. +STATIC mp_obj_t esp_isconnected(mp_obj_t self_in) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->if_id == STATION_IF) { + if (wifi_station_get_connect_status() == STATION_GOT_IP) { + return mp_const_true; + } + } else { + if (wifi_softap_get_station_num() > 0) { + return mp_const_true; + } + } + return mp_const_false; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_isconnected_obj, esp_isconnected); + +STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + struct ip_info info; + ip_addr_t dns_addr; + wifi_get_ip_info(self->if_id, &info); + if (n_args == 1) { + // get + dns_addr = dns_getserver(0); + mp_obj_t tuple[4] = { + netutils_format_ipv4_addr((uint8_t *)&info.ip, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&info.netmask, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&info.gw, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&dns_addr, NETUTILS_BIG), + }; + return mp_obj_new_tuple(4, tuple); + } else if (args[1] == MP_OBJ_NEW_QSTR(MP_QSTR_dhcp)) { + // use DHCP to configure the IP addresses + require_if(args[0], STATION_IF); + wifi_station_dhcpc_start(); + return mp_const_none; + } else { + // set + mp_obj_t *items; + bool restart_dhcp_server = false; + mp_obj_get_array_fixed_n(args[1], 4, &items); + netutils_parse_ipv4_addr(items[0], (void *)&info.ip, NETUTILS_BIG); + if (mp_obj_is_integer(items[1])) { + // allow numeric netmask, i.e.: + // 24 -> 255.255.255.0 + // 16 -> 255.255.0.0 + // etc... + uint32_t *m = (uint32_t *)&info.netmask; + *m = htonl(0xffffffff << (32 - mp_obj_get_int(items[1]))); + } else { + netutils_parse_ipv4_addr(items[1], (void *)&info.netmask, NETUTILS_BIG); + } + netutils_parse_ipv4_addr(items[2], (void *)&info.gw, NETUTILS_BIG); + netutils_parse_ipv4_addr(items[3], (void *)&dns_addr, NETUTILS_BIG); + // To set a static IP we have to disable DHCP first + if (self->if_id == STATION_IF) { + wifi_station_dhcpc_stop(); + } else { + restart_dhcp_server = wifi_softap_dhcps_status(); + wifi_softap_dhcps_stop(); + } + if (!wifi_set_ip_info(self->if_id, &info)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("wifi_set_ip_info() failed")); + } + dns_setserver(0, &dns_addr); + if (restart_dhcp_server) { + wifi_softap_dhcps_start(); + } + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj, 1, 2, esp_ifconfig); + +STATIC mp_obj_t esp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + union { + struct station_config sta; + struct softap_config ap; + } cfg; + + if (self->if_id == STATION_IF) { + error_check(wifi_station_get_config(&cfg.sta), "can't get STA config"); + } else { + error_check(wifi_softap_get_config(&cfg.ap), "can't get AP config"); + } + + int req_if = -1; + + if (kwargs->used != 0) { + + for (mp_uint_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_mac: { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); + } + wifi_set_macaddr(self->if_id, bufinfo.buf); + break; + } + case MP_QSTR_essid: { + req_if = SOFTAP_IF; + size_t len; + const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); + len = MIN(len, sizeof(cfg.ap.ssid)); + memcpy(cfg.ap.ssid, s, len); + cfg.ap.ssid_len = len; + break; + } + case MP_QSTR_hidden: { + req_if = SOFTAP_IF; + cfg.ap.ssid_hidden = mp_obj_is_true(kwargs->table[i].value); + break; + } + case MP_QSTR_authmode: { + req_if = SOFTAP_IF; + cfg.ap.authmode = mp_obj_get_int(kwargs->table[i].value); + break; + } + case MP_QSTR_password: { + req_if = SOFTAP_IF; + size_t len; + const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); + len = MIN(len, sizeof(cfg.ap.password) - 1); + memcpy(cfg.ap.password, s, len); + cfg.ap.password[len] = 0; + break; + } + case MP_QSTR_channel: { + req_if = SOFTAP_IF; + cfg.ap.channel = mp_obj_get_int(kwargs->table[i].value); + break; + } + case MP_QSTR_dhcp_hostname: { + req_if = STATION_IF; + if (self->if_id == STATION_IF) { + const char *s = mp_obj_str_get_str(kwargs->table[i].value); + wifi_station_set_hostname((char *)s); + } + break; + } + default: + goto unknown; + } + } + } + + // We post-check interface requirements to save on code size + if (req_if >= 0) { + require_if(args[0], req_if); + } + + if (self->if_id == STATION_IF) { + error_check(wifi_station_set_config(&cfg.sta), "can't set STA config"); + } else { + error_check(wifi_softap_set_config(&cfg.ap), "can't set AP config"); + } + + return mp_const_none; + } + + // Get config + + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } + + mp_obj_t val; + + qstr key = mp_obj_str_get_qstr(args[1]); + switch (key) { + case MP_QSTR_mac: { + uint8_t mac[6]; + wifi_get_macaddr(self->if_id, mac); + return mp_obj_new_bytes(mac, sizeof(mac)); + } + case MP_QSTR_essid: + if (self->if_id == STATION_IF) { + val = mp_obj_new_str((char *)cfg.sta.ssid, strlen((char *)cfg.sta.ssid)); + } else { + val = mp_obj_new_str((char *)cfg.ap.ssid, cfg.ap.ssid_len); + } + break; + case MP_QSTR_hidden: + req_if = SOFTAP_IF; + val = mp_obj_new_bool(cfg.ap.ssid_hidden); + break; + case MP_QSTR_authmode: + req_if = SOFTAP_IF; + val = MP_OBJ_NEW_SMALL_INT(cfg.ap.authmode); + break; + case MP_QSTR_channel: + req_if = SOFTAP_IF; + val = MP_OBJ_NEW_SMALL_INT(cfg.ap.channel); + break; + case MP_QSTR_dhcp_hostname: { + req_if = STATION_IF; + char *s = wifi_station_get_hostname(); + if (s == NULL) { + val = MP_OBJ_NEW_QSTR(MP_QSTR_); + } else { + val = mp_obj_new_str(s, strlen(s)); + } + break; + } + default: + goto unknown; + } + + // We post-check interface requirements to save on code size + if (req_if >= 0) { + require_if(args[0], req_if); + } + + return val; + +unknown: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp_config_obj, 1, esp_config); + +STATIC const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&esp_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&esp_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&esp_disconnect_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&esp_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&esp_scan_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&esp_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&esp_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_ifconfig_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); + +const mp_obj_type_t wlan_if_type = { + { &mp_type_type }, + .name = MP_QSTR_WLAN, + .locals_dict = (mp_obj_dict_t *)&wlan_if_locals_dict, +}; + +STATIC mp_obj_t esp_phy_mode(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return mp_obj_new_int(wifi_get_phy_mode()); + } else { + wifi_set_phy_mode(mp_obj_get_int(args[0])); + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_phy_mode_obj, 0, 1, esp_phy_mode); + +STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, + { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&get_wlan_obj) }, + { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_phy_mode_obj) }, + + #if MODNETWORK_INCLUDE_CONSTANTS + { MP_ROM_QSTR(MP_QSTR_STA_IF), MP_ROM_INT(STATION_IF)}, + { MP_ROM_QSTR(MP_QSTR_AP_IF), MP_ROM_INT(SOFTAP_IF)}, + + { MP_ROM_QSTR(MP_QSTR_STAT_IDLE), MP_ROM_INT(STATION_IDLE)}, + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECTING), MP_ROM_INT(STATION_CONNECTING)}, + { MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(STATION_WRONG_PASSWORD)}, + { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND), MP_ROM_INT(STATION_NO_AP_FOUND)}, + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECT_FAIL), MP_ROM_INT(STATION_CONNECT_FAIL)}, + { MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(STATION_GOT_IP)}, + + { MP_ROM_QSTR(MP_QSTR_MODE_11B), MP_ROM_INT(PHY_MODE_11B) }, + { MP_ROM_QSTR(MP_QSTR_MODE_11G), MP_ROM_INT(PHY_MODE_11G) }, + { MP_ROM_QSTR(MP_QSTR_MODE_11N), MP_ROM_INT(PHY_MODE_11N) }, + + { MP_ROM_QSTR(MP_QSTR_AUTH_OPEN), MP_ROM_INT(AUTH_OPEN) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WEP), MP_ROM_INT(AUTH_WEP) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA_PSK), MP_ROM_INT(AUTH_WPA_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_PSK), MP_ROM_INT(AUTH_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA_WPA2_PSK), MP_ROM_INT(AUTH_WPA_WPA2_PSK) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_network_globals, mp_module_network_globals_table); + +const mp_obj_module_t network_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_network_globals, +}; diff --git a/ports/esp8266/modpyb.c b/ports/esp8266/modpyb.c new file mode 100644 index 0000000000..0a23f6f9da --- /dev/null +++ b/ports/esp8266/modpyb.c @@ -0,0 +1,79 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/gc.h" +#include "gccollect.h" +#include "modmachine.h" + +// The pyb module no longer exists since all functionality now appears +// elsewhere, in more standard places (eg time, machine modules). The +// only remaining function is pyb.info() which has been moved to the +// esp module, pending deletion/renaming/moving elsewhere. + +STATIC mp_obj_t pyb_info(size_t n_args, const mp_obj_t *args) { + // print info about memory + { + printf("_text_start=%p\n", &_text_start); + printf("_text_end=%p\n", &_text_end); + printf("_irom0_text_start=%p\n", &_irom0_text_start); + printf("_irom0_text_end=%p\n", &_irom0_text_end); + printf("_data_start=%p\n", &_data_start); + printf("_data_end=%p\n", &_data_end); + printf("_rodata_start=%p\n", &_rodata_start); + printf("_rodata_end=%p\n", &_rodata_end); + printf("_bss_start=%p\n", &_bss_start); + printf("_bss_end=%p\n", &_bss_end); + printf("_heap_start=%p\n", &_heap_start); + printf("_heap_end=%p\n", &_heap_end); + } + + // qstr info + { + mp_uint_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; + qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); + printf("qstr:\n n_pool=" UINT_FMT "\n n_qstr=" UINT_FMT "\n n_str_data_bytes=" UINT_FMT "\n n_total_bytes=" UINT_FMT "\n", n_pool, n_qstr, n_str_data_bytes, n_total_bytes); + } + + // GC info + { + gc_info_t info; + gc_info(&info); + printf("GC:\n"); + printf(" " UINT_FMT " total\n", info.total); + printf(" " UINT_FMT " : " UINT_FMT "\n", info.used, info.free); + printf(" 1=" UINT_FMT " 2=" UINT_FMT " m=" UINT_FMT "\n", info.num_1block, info.num_2block, info.max_block); + } + + if (n_args == 1) { + // arg given means dump gc allocation table + gc_dump_alloc_table(); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_info_obj, 0, 1, pyb_info); diff --git a/ports/esp8266/modules/_boot.py b/ports/esp8266/modules/_boot.py new file mode 100644 index 0000000000..1f77d88024 --- /dev/null +++ b/ports/esp8266/modules/_boot.py @@ -0,0 +1,15 @@ +import gc + +gc.threshold((gc.mem_free() + gc.mem_alloc()) // 4) +import uos +from flashbdev import bdev + +if bdev: + try: + uos.mount(bdev, "/") + except: + import inisetup + + inisetup.setup() + +gc.collect() diff --git a/ports/esp8266/modules/apa102.py b/ports/esp8266/modules/apa102.py new file mode 100644 index 0000000000..41b7c0485c --- /dev/null +++ b/ports/esp8266/modules/apa102.py @@ -0,0 +1,17 @@ +# APA102 driver for MicroPython on ESP8266 +# MIT license; Copyright (c) 2016 Robert Foss, Daniel Busch + +from esp import apa102_write +from neopixel import NeoPixel + + +class APA102(NeoPixel): + ORDER = (0, 1, 2, 3) + + def __init__(self, clock_pin, data_pin, n, bpp=4): + super().__init__(data_pin, n, bpp) + self.clock_pin = clock_pin + self.clock_pin.init(clock_pin.OUT) + + def write(self): + apa102_write(self.clock_pin, self.pin, self.buf) diff --git a/ports/esp8266/modules/flashbdev.py b/ports/esp8266/modules/flashbdev.py new file mode 100644 index 0000000000..652fe9a4ba --- /dev/null +++ b/ports/esp8266/modules/flashbdev.py @@ -0,0 +1,42 @@ +import esp + + +class FlashBdev: + SEC_SIZE = 4096 + + def __init__(self, start_sec, blocks): + self.start_sec = start_sec + self.blocks = blocks + + def readblocks(self, n, buf, off=0): + # print("readblocks(%s, %x(%d), %d)" % (n, id(buf), len(buf), off)) + esp.flash_read((n + self.start_sec) * self.SEC_SIZE + off, buf) + + def writeblocks(self, n, buf, off=None): + # print("writeblocks(%s, %x(%d), %d)" % (n, id(buf), len(buf), off)) + # assert len(buf) <= self.SEC_SIZE, len(buf) + if off is None: + esp.flash_erase(n + self.start_sec) + off = 0 + esp.flash_write((n + self.start_sec) * self.SEC_SIZE + off, buf) + + def ioctl(self, op, arg): + # print("ioctl(%d, %r)" % (op, arg)) + if op == 4: # MP_BLOCKDEV_IOCTL_BLOCK_COUNT + return self.blocks + if op == 5: # MP_BLOCKDEV_IOCTL_BLOCK_SIZE + return self.SEC_SIZE + if op == 6: # MP_BLOCKDEV_IOCTL_BLOCK_ERASE + esp.flash_erase(arg + self.start_sec) + return 0 + + +size = esp.flash_size() +if size < 1024 * 1024: + bdev = None +else: + start_sec = esp.flash_user_start() // FlashBdev.SEC_SIZE + if start_sec < 256: + start_sec += 1 # Reserve space for native code + # 20K at the flash end is reserved for SDK params storage + bdev = FlashBdev(start_sec, (size - 20480) // FlashBdev.SEC_SIZE - start_sec) diff --git a/ports/esp8266/modules/inisetup.py b/ports/esp8266/modules/inisetup.py new file mode 100644 index 0000000000..9500b8048c --- /dev/null +++ b/ports/esp8266/modules/inisetup.py @@ -0,0 +1,64 @@ +import uos +import network +from flashbdev import bdev + + +def wifi(): + import ubinascii + + ap_if = network.WLAN(network.AP_IF) + essid = b"MicroPython-%s" % ubinascii.hexlify(ap_if.config("mac")[-3:]) + ap_if.config(essid=essid, authmode=network.AUTH_WPA_WPA2_PSK, password=b"micropythoN") + + +def check_bootsec(): + buf = bytearray(bdev.SEC_SIZE) + bdev.readblocks(0, buf) + empty = True + for b in buf: + if b != 0xFF: + empty = False + break + if empty: + return True + fs_corrupted() + + +def fs_corrupted(): + import time + + while 1: + print( + """\ +The filesystem starting at sector %d with size %d sectors looks corrupt. +You may want to make a flash snapshot and try to recover it. Otherwise, +format it with uos.VfsLfs2.mkfs(bdev), or completely erase the flash and +reprogram MicroPython. +""" + % (bdev.start_sec, bdev.blocks) + ) + time.sleep(3) + + +def setup(): + check_bootsec() + print("Performing initial setup") + wifi() + uos.VfsLfs2.mkfs(bdev) + vfs = uos.VfsLfs2(bdev) + uos.mount(vfs, "/") + with open("boot.py", "w") as f: + f.write( + """\ +# This file is executed on every boot (including wake-boot from deepsleep) +#import esp +#esp.osdebug(None) +import uos, machine +#uos.dupterm(None, 1) # disable REPL on UART(0) +import gc +#import webrepl +#webrepl.start() +gc.collect() +""" + ) + return vfs diff --git a/ports/esp32/modules/ntptime.py b/ports/esp8266/modules/ntptime.py similarity index 100% rename from ports/esp32/modules/ntptime.py rename to ports/esp8266/modules/ntptime.py diff --git a/ports/esp8266/modules/port_diag.py b/ports/esp8266/modules/port_diag.py new file mode 100644 index 0000000000..f2c69ecacd --- /dev/null +++ b/ports/esp8266/modules/port_diag.py @@ -0,0 +1,38 @@ +import esp +import uctypes +import network +import lwip + + +def main(): + + ROM = uctypes.bytearray_at(0x40200000, 16) + fid = esp.flash_id() + + print("FlashROM:") + print("Flash ID: %x (Vendor: %x Device: %x)" % (fid, fid & 0xFF, fid & 0xFF00 | fid >> 16)) + + print("Flash bootloader data:") + SZ_MAP = {0: "512KB", 1: "256KB", 2: "1MB", 3: "2MB", 4: "4MB"} + FREQ_MAP = {0: "40MHZ", 1: "26MHZ", 2: "20MHz", 0xF: "80MHz"} + print("Byte @2: %02x" % ROM[2]) + print( + "Byte @3: %02x (Flash size: %s Flash freq: %s)" + % (ROM[3], SZ_MAP.get(ROM[3] >> 4, "?"), FREQ_MAP.get(ROM[3] & 0xF)) + ) + print("Firmware checksum:") + print(esp.check_fw()) + + print("\nNetworking:") + print("STA ifconfig:", network.WLAN(network.STA_IF).ifconfig()) + print("AP ifconfig:", network.WLAN(network.AP_IF).ifconfig()) + print("Free WiFi driver buffers of type:") + for i, comm in enumerate( + ("1,2 TX", "4 Mngmt TX(len: 0x41-0x100)", "5 Mngmt TX (len: 0-0x40)", "7", "8 RX") + ): + print("%d: %d (%s)" % (i, esp.esf_free_bufs(i), comm)) + print("lwIP PCBs:") + lwip.print_pcbs() + + +main() diff --git a/ports/esp8266/moduos.c b/ports/esp8266/moduos.c new file mode 100644 index 0000000000..f04094fbed --- /dev/null +++ b/ports/esp8266/moduos.c @@ -0,0 +1,139 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Josef Gajdusek + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/objtuple.h" +#include "py/objstr.h" +#include "extmod/misc.h" +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "extmod/vfs_lfs.h" +#include "genhdr/mpversion.h" +#include "esp_mphal.h" +#include "user_interface.h" + +STATIC const qstr os_uname_info_fields[] = { + MP_QSTR_sysname, MP_QSTR_nodename, + MP_QSTR_release, MP_QSTR_version, MP_QSTR_machine +}; +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_sysname_obj, MICROPY_PY_SYS_PLATFORM); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_nodename_obj, MICROPY_PY_SYS_PLATFORM); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_version_obj, MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_machine_obj, MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME); + +STATIC mp_obj_tuple_t os_uname_info_obj = { + .base = {&mp_type_attrtuple}, + .len = 5, + .items = { + (mp_obj_t)&os_uname_info_sysname_obj, + (mp_obj_t)&os_uname_info_nodename_obj, + NULL, + (mp_obj_t)&os_uname_info_version_obj, + (mp_obj_t)&os_uname_info_machine_obj, + (void *)os_uname_info_fields, + } +}; + +STATIC mp_obj_t os_uname(void) { + // We must populate the "release" field each time in case it was GC'd since the last call. + const char *ver = system_get_sdk_version(); + os_uname_info_obj.items[2] = mp_obj_new_str(ver, strlen(ver)); + return (mp_obj_t)&os_uname_info_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_uname_obj, os_uname); + +STATIC mp_obj_t os_urandom(mp_obj_t num) { + mp_int_t n = mp_obj_get_int(num); + vstr_t vstr; + vstr_init_len(&vstr, n); + for (int i = 0; i < n; i++) { + vstr.buf[i] = *WDEV_HWRNG; + } + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_urandom_obj, os_urandom); + +// We wrap the mp_uos_dupterm function to detect if a UART is attached or not +mp_obj_t os_dupterm(size_t n_args, const mp_obj_t *args) { + mp_obj_t prev_obj = mp_uos_dupterm_obj.fun.var(n_args, args); + if (mp_obj_get_type(args[0]) == &pyb_uart_type) { + ++uart_attached_to_dupterm; + } + if (mp_obj_get_type(prev_obj) == &pyb_uart_type) { + --uart_attached_to_dupterm; + } + return prev_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(os_dupterm_obj, 1, 2, os_dupterm); + +STATIC mp_obj_t os_dupterm_notify(mp_obj_t obj_in) { + (void)obj_in; + mp_hal_signal_dupterm_input(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(os_dupterm_notify_obj, os_dupterm_notify); + +STATIC const mp_rom_map_elem_t os_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, + { MP_ROM_QSTR(MP_QSTR_uname), MP_ROM_PTR(&os_uname_obj) }, + { MP_ROM_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&os_urandom_obj) }, + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&os_dupterm_obj) }, + { MP_ROM_QSTR(MP_QSTR_dupterm_notify), MP_ROM_PTR(&os_dupterm_notify_obj) }, + #endif + #if MICROPY_VFS + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mp_vfs_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mp_vfs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&mp_vfs_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&mp_vfs_getcwd_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mp_vfs_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&mp_vfs_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mp_vfs_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) }, + { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&mp_vfs_mount_obj) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_vfs_umount_obj) }, + #if MICROPY_VFS_FAT + { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, + #endif + #if MICROPY_VFS_LFS1 + { MP_ROM_QSTR(MP_QSTR_VfsLfs1), MP_ROM_PTR(&mp_type_vfs_lfs1) }, + #endif + #if MICROPY_VFS_LFS2 + { MP_ROM_QSTR(MP_QSTR_VfsLfs2), MP_ROM_PTR(&mp_type_vfs_lfs2) }, + #endif + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table); + +const mp_obj_module_t uos_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&os_module_globals, +}; diff --git a/ports/esp8266/modutime.c b/ports/esp8266/modutime.c new file mode 100644 index 0000000000..1d4ecc05f2 --- /dev/null +++ b/ports/esp8266/modutime.c @@ -0,0 +1,131 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2015 Josef Gajdusek + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/gc.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/smallint.h" +#include "shared/timeutils/timeutils.h" +#include "modmachine.h" +#include "user_interface.h" +#include "extmod/utime_mphal.h" + +/// \module time - time related functions +/// +/// The `time` module provides functions for getting the current time and date, +/// and for sleeping. + +/// \function localtime([secs]) +/// Convert a time expressed in seconds since Jan 1, 2000 into an 8-tuple which +/// contains: (year, month, mday, hour, minute, second, weekday, yearday) +/// If secs is not provided or None, then the current time from the RTC is used. +/// year includes the century (for example 2014) +/// month is 1-12 +/// mday is 1-31 +/// hour is 0-23 +/// minute is 0-59 +/// second is 0-59 +/// weekday is 0-6 for Mon-Sun. +/// yearday is 1-366 +STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { + timeutils_struct_time_t tm; + mp_int_t seconds; + if (n_args == 0 || args[0] == mp_const_none) { + seconds = pyb_rtc_get_us_since_epoch() / 1000 / 1000; + } else { + seconds = mp_obj_get_int(args[0]); + } + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + mp_obj_t tuple[8] = { + tuple[0] = mp_obj_new_int(tm.tm_year), + tuple[1] = mp_obj_new_int(tm.tm_mon), + tuple[2] = mp_obj_new_int(tm.tm_mday), + tuple[3] = mp_obj_new_int(tm.tm_hour), + tuple[4] = mp_obj_new_int(tm.tm_min), + tuple[5] = mp_obj_new_int(tm.tm_sec), + tuple[6] = mp_obj_new_int(tm.tm_wday), + tuple[7] = mp_obj_new_int(tm.tm_yday), + }; + return mp_obj_new_tuple(8, tuple); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_localtime_obj, 0, 1, time_localtime); + +/// \function mktime() +/// This is inverse function of localtime. It's argument is a full 8-tuple +/// which expresses a time as per localtime. It returns an integer which is +/// the number of seconds since Jan 1, 2000. +STATIC mp_obj_t time_mktime(mp_obj_t tuple) { + size_t len; + mp_obj_t *elem; + mp_obj_get_array(tuple, &len, &elem); + + // localtime generates a tuple of len 8. CPython uses 9, so we accept both. + if (len < 8 || len > 9) { + mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("mktime needs a tuple of length 8 or 9 (%d given)"), len); + } + + return mp_obj_new_int_from_uint(timeutils_mktime(mp_obj_get_int(elem[0]), + mp_obj_get_int(elem[1]), mp_obj_get_int(elem[2]), mp_obj_get_int(elem[3]), + mp_obj_get_int(elem[4]), mp_obj_get_int(elem[5]))); +} +MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime); + +/// \function time() +/// Returns the number of seconds, as an integer, since the Epoch. +STATIC mp_obj_t time_time(void) { + // get date and time + return mp_obj_new_int(pyb_rtc_get_us_since_epoch() / 1000 / 1000); +} +MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); + +STATIC const mp_rom_map_elem_t time_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); + +const mp_obj_module_t utime_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&time_module_globals, +}; diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h new file mode 100644 index 0000000000..7d6d0a34c3 --- /dev/null +++ b/ports/esp8266/mpconfigport.h @@ -0,0 +1,214 @@ +// Options to control how MicroPython is built for this port, +// overriding defaults in py/mpconfig.h. + +// Board-specific definitions +#include "mpconfigboard.h" + +#include + +#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) +#define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#define MICROPY_ALLOC_PATH_MAX (128) +#define MICROPY_ALLOC_LEXER_INDENT_INIT (8) +#define MICROPY_ALLOC_PARSE_RULE_INIT (48) +#define MICROPY_ALLOC_PARSE_RULE_INC (8) +#define MICROPY_ALLOC_PARSE_RESULT_INC (8) +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) +#define MICROPY_MEM_STATS (0) +#define MICROPY_DEBUG_PRINTER (&mp_debug_print) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_STACK_CHECK (1) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_KBD_EXCEPTION (1) +#define MICROPY_REPL_EVENT_DRIVEN (0) +#define MICROPY_REPL_AUTO_INDENT (1) +#define MICROPY_HELPER_REPL (1) +#define MICROPY_HELPER_LEXER_UNIX (0) +#define MICROPY_ENABLE_SOURCE_LINE (1) +#define MICROPY_MODULE_BUILTIN_INIT (1) +#define MICROPY_MODULE_WEAK_LINKS (1) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) +#define MICROPY_USE_INTERNAL_ERRNO (1) +#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_BUILTINS_COMPLEX (0) +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_FROZENSET (1) +#define MICROPY_PY_BUILTINS_SET (1) +#define MICROPY_PY_BUILTINS_SLICE (1) +#define MICROPY_PY_BUILTINS_PROPERTY (1) +#define MICROPY_PY_BUILTINS_ROUND_INT (1) +#define MICROPY_PY_BUILTINS_INPUT (1) +#define MICROPY_PY_BUILTINS_HELP (1) +#define MICROPY_PY_BUILTINS_HELP_TEXT esp_help_text +#define MICROPY_PY_BUILTINS_HELP_MODULES (1) +#define MICROPY_PY___FILE__ (0) +#define MICROPY_PY_GC (1) +#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#define MICROPY_PY_COLLECTIONS (1) +#define MICROPY_PY_COLLECTIONS_DEQUE (1) +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#define MICROPY_PY_MATH (1) +#define MICROPY_PY_CMATH (0) +#define MICROPY_PY_IO (1) +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_STRUCT (1) +#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_EXIT (1) +#define MICROPY_PY_SYS_STDFILES (1) +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UHASHLIB (1) +#define MICROPY_PY_UHASHLIB_SHA1 (MICROPY_PY_USSL && MICROPY_SSL_AXTLS) +#define MICROPY_PY_UHEAPQ (1) +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (*WDEV_HWRNG) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_USELECT (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_UZLIB (1) +#define MICROPY_PY_LWIP (1) +#define MICROPY_PY_LWIP_SOCK_RAW (1) +#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new +#define MICROPY_PY_MACHINE_BITSTREAM (1) +#define MICROPY_PY_MACHINE_PULSE (1) +#define MICROPY_PY_MACHINE_PWM (1) +#define MICROPY_PY_MACHINE_PWM_INIT (1) +#define MICROPY_PY_MACHINE_PWM_DUTY (1) +#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/esp8266/machine_pwm.c" +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_SOFTI2C (1) +#define MICROPY_PY_MACHINE_SPI (1) +#define MICROPY_PY_MACHINE_SOFTSPI (1) +#define MICROPY_PY_UWEBSOCKET (1) +#define MICROPY_PY_ONEWIRE (1) +#define MICROPY_PY_WEBREPL (1) +#define MICROPY_PY_WEBREPL_DELAY (20) +#define MICROPY_PY_WEBREPL_STATIC_FILEBUF (1) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_PY_OS_DUPTERM (2) +#define MICROPY_CPYTHON_COMPAT (1) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +#define MICROPY_WARNINGS (1) +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) +#define MICROPY_STREAMS_NON_BLOCK (1) +#define MICROPY_STREAMS_POSIX_API (1) +#define MICROPY_MODULE_FROZEN_LEXER mp_lexer_new_from_str32 + +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ +#define MICROPY_ESP8266_APA102 (1) + +#define MICROPY_EVENT_POLL_HOOK {ets_event_poll();} +#define MICROPY_VM_HOOK_COUNT (10) +#define MICROPY_VM_HOOK_INIT static uint vm_hook_divisor = MICROPY_VM_HOOK_COUNT; +#define MICROPY_VM_HOOK_POLL if (--vm_hook_divisor == 0) { \ + vm_hook_divisor = MICROPY_VM_HOOK_COUNT; \ + extern void ets_loop_iter(void); \ + ets_loop_iter(); \ +} +#define MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_POLL +#define MICROPY_VM_HOOK_RETURN MICROPY_VM_HOOK_POLL + +#include "xtirq.h" +#define MICROPY_BEGIN_ATOMIC_SECTION() disable_irq() +#define MICROPY_END_ATOMIC_SECTION(state) enable_irq(state) + +// type definitions for the specific machine + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p))) + +#define MP_SSIZE_MAX (0x7fffffff) + +#define UINT_FMT "%u" +#define INT_FMT "%d" + +typedef int32_t mp_int_t; // must be pointer size +typedef uint32_t mp_uint_t; // must be pointer size +typedef long mp_off_t; +typedef uint32_t sys_prot_t; // for modlwip +// ssize_t, off_t as required by POSIX-signatured functions in stream.h +#include + +void *esp_native_code_commit(void *, size_t, void *); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) + +// printer for debugging output, goes to UART only +extern const struct _mp_print_t mp_debug_print; + +#if MICROPY_VFS_FAT +#define mp_type_fileio mp_type_vfs_fat_fileio +#define mp_type_textio mp_type_vfs_fat_textio +#elif MICROPY_VFS_LFS1 +#define mp_type_fileio mp_type_vfs_lfs1_fileio +#define mp_type_textio mp_type_vfs_lfs1_textio +#elif MICROPY_VFS_LFS2 +#define mp_type_fileio mp_type_vfs_lfs2_fileio +#define mp_type_textio mp_type_vfs_lfs2_textio +#endif + +// use vfs's functions for import stat and builtin open +#define mp_import_stat mp_vfs_import_stat +#define mp_builtin_open mp_vfs_open +#define mp_builtin_open_obj mp_vfs_open_obj + +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + +// extra built in modules to add to the list of known ones +extern const struct _mp_obj_module_t esp_module; +extern const struct _mp_obj_module_t network_module; +extern const struct _mp_obj_module_t utime_module; +extern const struct _mp_obj_module_t uos_module; +extern const struct _mp_obj_module_t mp_module_lwip; +extern const struct _mp_obj_module_t mp_module_machine; +extern const struct _mp_obj_module_t mp_module_onewire; + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_ROM_QSTR(MP_QSTR_esp), MP_ROM_PTR(&esp_module) }, \ + { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_lwip) }, \ + { MP_ROM_QSTR(MP_QSTR_network), MP_ROM_PTR(&network_module) }, \ + { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&utime_module) }, \ + { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&uos_module) }, \ + { MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&mp_module_machine) }, \ + { MP_ROM_QSTR(MP_QSTR__onewire), MP_ROM_PTR(&mp_module_onewire) }, \ + +#define MP_STATE_PORT MP_STATE_VM + +#define MICROPY_PORT_ROOT_POINTERS \ + const char *readline_hist[8]; \ + mp_obj_t pin_irq_handler[16]; \ + byte *uart0_rxbuf; \ + +// We need an implementation of the log2 function which is not a macro +#define MP_NEED_LOG2 (1) + +// We need to provide a declaration/definition of alloca() +#include + +// board specifics + +#define MICROPY_MPHALPORT_H "esp_mphal.h" +#define MICROPY_PY_SYS_PLATFORM "esp8266" + +#define MP_FASTCODE(n) __attribute__((section(".iram0.text." #n))) n +#define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) MP_FASTCODE(f) +#define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) MP_FASTCODE(f) +#define MICROPY_WRAP_MP_SCHED_SCHEDULE(f) MP_FASTCODE(f) + +#define WDEV_HWRNG ((volatile uint32_t *)0x3ff20e44) + +#define _assert(expr) ((expr) ? (void)0 : __assert_func(__FILE__, __LINE__, __func__, #expr)) diff --git a/ports/esp32/utility.c b/ports/esp8266/posix_helpers.c similarity index 55% rename from ports/esp32/utility.c rename to ports/esp8266/posix_helpers.c index 54f085938a..b72c4ff9d6 100644 --- a/ports/esp32/utility.c +++ b/ports/esp8266/posix_helpers.c @@ -1,11 +1,9 @@ /* * This file is part of the MicroPython project, http://micropython.org/ * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * * The MIT License (MIT) * - * Copyright (c) Alibaba AIoT + * Copyright (c) 2016 Paul Sokolovsky * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,34 +24,50 @@ * THE SOFTWARE. */ -#include "utility.h" +#include +#include +#include +#include "py/mphal.h" +#include "py/gc.h" + +// Functions for external libs like axTLS, BerkeleyDB, etc. -bool callback_to_python_LoBo(mp_obj_t function, mp_obj_t arg, void *carg) -{ - bool ret = false; - if (function != MP_OBJ_NULL && mp_obj_is_callable(function)) { - ret = mp_sched_schedule_LoBo(function, arg, carg); - if (ret == false) { - printf("[utility]: schedule queue is full !!!!\r\n"); - } - mp_hal_wake_main_task_from_isr(); +void *malloc(size_t size) { + void *p = gc_alloc(size, false); + if (p == NULL) { + // POSIX requires ENOMEM to be set in case of error + errno = ENOMEM; } - return ret; + return p; +} +void free(void *ptr) { + gc_free(ptr); +} +void *calloc(size_t nmemb, size_t size) { + return malloc(nmemb * size); +} +void *realloc(void *ptr, size_t size) { + void *p = gc_realloc(ptr, size, true); + if (p == NULL) { + // POSIX requires ENOMEM to be set in case of error + errno = ENOMEM; + } + return p; } -mp_obj_t mp_obj_new_strn(const char *data) -{ - return mp_obj_new_str(data, strlen(data)); +#undef htonl +#undef ntohl +uint32_t ntohl(uint32_t netlong) { + return MP_BE32TOH(netlong); +} +uint32_t htonl(uint32_t netlong) { + return MP_HTOBE32(netlong); } -const char *get_str_from_dict(mp_obj_t dict, const char *key) -{ - mp_obj_t value_obj = mp_obj_dict_get(dict, mp_obj_new_strn(key)); - return (char *)mp_obj_str_get_str(value_obj); +time_t time(time_t *t) { + return mp_hal_ticks_ms() / 1000; } -int get_int_from_dict(mp_obj_t dict, const char *key) -{ - mp_obj_t value_obj = mp_obj_dict_get(dict, mp_obj_new_strn(key)); - return mp_obj_get_int(value_obj); +time_t mktime(void *tm) { + return 0; } diff --git a/ports/esp8266/qstrdefsport.h b/ports/esp8266/qstrdefsport.h new file mode 100644 index 0000000000..b84790c66e --- /dev/null +++ b/ports/esp8266/qstrdefsport.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// qstrs specific to this port, only needed if they aren't auto-generated +// *FORMAT-OFF* + +// Entries for sys.path +Q(/) +Q(/lib) diff --git a/ports/esp8266/strtoll.c b/ports/esp8266/strtoll.c new file mode 100644 index 0000000000..f095f7606d --- /dev/null +++ b/ports/esp8266/strtoll.c @@ -0,0 +1,29 @@ +#include + +// assumes endptr != NULL +// doesn't check for sign +// doesn't check for base-prefix +long long int strtoll(const char *nptr, char **endptr, int base) { + long long val = 0; + + for (; *nptr; nptr++) { + int v = *nptr; + if ('0' <= v && v <= '9') { + v -= '0'; + } else if ('A' <= v && v <= 'Z') { + v -= 'A' - 10; + } else if ('a' <= v && v <= 'z') { + v -= 'a' - 10; + } else { + break; + } + if (v >= base) { + break; + } + val = val * base + v; + } + + *endptr = (char *)nptr; + + return val; +} diff --git a/ports/esp8266/uart.c b/ports/esp8266/uart.c new file mode 100644 index 0000000000..978a7efc38 --- /dev/null +++ b/ports/esp8266/uart.c @@ -0,0 +1,322 @@ +/****************************************************************************** + * Copyright 2013-2014 Espressif Systems (Wuxi) + * + * FileName: uart.c + * + * Description: Two UART mode configration and interrupt handler. + * Check your hardware connection while use this mode. + * + * Modification history: + * 2014/3/12, v1.0 create this file. +*******************************************************************************/ +#include "ets_sys.h" +#include "osapi.h" +#include "uart.h" +#include "osapi.h" +#include "uart_register.h" +#include "etshal.h" +#include "c_types.h" +#include "user_interface.h" +#include "esp_mphal.h" + +// seems that this is missing in the Espressif SDK +#define FUNC_U0RXD 0 + +#define UART_REPL UART0 + +// UartDev is defined and initialized in rom code. +extern UartDevice UartDev; + +// the uart to which OS messages go; -1 to disable +static int uart_os = UART_OS; + +#if MICROPY_REPL_EVENT_DRIVEN +static os_event_t uart_evt_queue[16]; +#endif + +// A small, static ring buffer for incoming chars +// This will only be populated if the UART is not attached to dupterm +uint8 uart_ringbuf_array[UART0_STATIC_RXBUF_LEN]; +static ringbuf_t uart_ringbuf = {uart_ringbuf_array, sizeof(uart_ringbuf_array), 0, 0}; + +static void uart0_rx_intr_handler(void *para); + +void soft_reset(void); +void mp_sched_keyboard_interrupt(void); + +/****************************************************************************** + * FunctionName : uart_config + * Description : Internal used function + * UART0 used for data TX/RX, RX buffer size is 0x100, interrupt enabled + * UART1 just used for debug output + * Parameters : uart_no, use UART0 or UART1 defined ahead + * Returns : NONE +*******************************************************************************/ +static void ICACHE_FLASH_ATTR uart_config(uint8 uart_no) { + if (uart_no == UART1) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK); + } else { + ETS_UART_INTR_ATTACH(uart0_rx_intr_handler, NULL); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD); + } + + uart_div_modify(uart_no, UART_CLK_FREQ / (UartDev.baut_rate)); + + WRITE_PERI_REG(UART_CONF0(uart_no), UartDev.exist_parity + | UartDev.parity + | (UartDev.stop_bits << UART_STOP_BIT_NUM_S) + | (UartDev.data_bits << UART_BIT_NUM_S)); + + // clear rx and tx fifo,not ready + SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST); + CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST); + + if (uart_no == UART0) { + // set rx fifo trigger + WRITE_PERI_REG(UART_CONF1(uart_no), + ((0x10 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) | + ((0x10 & UART_RX_FLOW_THRHD) << UART_RX_FLOW_THRHD_S) | + UART_RX_FLOW_EN | + (0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S | + UART_RX_TOUT_EN); + SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_TOUT_INT_ENA | + UART_FRM_ERR_INT_ENA); + } else { + WRITE_PERI_REG(UART_CONF1(uart_no), + ((UartDev.rcv_buff.TrigLvl & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S)); + } + + // clear all interrupt + WRITE_PERI_REG(UART_INT_CLR(uart_no), 0xffff); + // enable rx_interrupt + SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA); +} + +/****************************************************************************** + * FunctionName : uart1_tx_one_char + * Description : Internal used function + * Use uart1 interface to transfer one char + * Parameters : uint8 TxChar - character to tx + * Returns : OK +*******************************************************************************/ +void uart_tx_one_char(uint8 uart, uint8 TxChar) { + while (true) { + uint32 fifo_cnt = READ_PERI_REG(UART_STATUS(uart)) & (UART_TXFIFO_CNT << UART_TXFIFO_CNT_S); + if ((fifo_cnt >> UART_TXFIFO_CNT_S & UART_TXFIFO_CNT) < 126) { + break; + } + } + WRITE_PERI_REG(UART_FIFO(uart), TxChar); +} + +void uart_flush(uint8 uart) { + while (true) { + uint32 fifo_cnt = READ_PERI_REG(UART_STATUS(uart)) & (UART_TXFIFO_CNT << UART_TXFIFO_CNT_S); + if ((fifo_cnt >> UART_TXFIFO_CNT_S & UART_TXFIFO_CNT) == 0) { + break; + } + } +} + +/****************************************************************************** + * FunctionName : uart1_write_char + * Description : Internal used function + * Do some special deal while tx char is '\r' or '\n' + * Parameters : char c - character to tx + * Returns : NONE +*******************************************************************************/ +static void ICACHE_FLASH_ATTR +uart_os_write_char(char c) { + if (uart_os == -1) { + return; + } + if (c == '\n') { + uart_tx_one_char(uart_os, '\r'); + uart_tx_one_char(uart_os, '\n'); + } else if (c == '\r') { + } else { + uart_tx_one_char(uart_os, c); + } +} + +void ICACHE_FLASH_ATTR +uart_os_config(int uart) { + uart_os = uart; +} + +/****************************************************************************** + * FunctionName : uart0_rx_intr_handler + * Description : Internal used function + * UART0 interrupt handler, add self handle code inside + * Parameters : void *para - point to ETS_UART_INTR_ATTACH's arg + * Returns : NONE +*******************************************************************************/ + +static void uart0_rx_intr_handler(void *para) { + /* uart0 and uart1 intr combine togther, when interrupt occur, see reg 0x3ff20020, bit2, bit0 represents + * uart1 and uart0 respectively + */ + + uint8 uart_no = UART_REPL; + + if (UART_FRM_ERR_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_FRM_ERR_INT_ST)) { + // frame error + WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_FRM_ERR_INT_CLR); + } + + if (UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_FULL_INT_ST)) { + // fifo full + goto read_chars; + } else if (UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_TOUT_INT_ST)) { + read_chars: + ETS_UART_INTR_DISABLE(); + + while (READ_PERI_REG(UART_STATUS(uart_no)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) { + uint8 RcvChar = READ_PERI_REG(UART_FIFO(uart_no)) & 0xff; + // For efficiency, when connected to dupterm we put incoming chars + // directly on stdin_ringbuf, rather than going via uart_ringbuf + if (uart_attached_to_dupterm) { + if (RcvChar == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } else { + ringbuf_put(&stdin_ringbuf, RcvChar); + } + } else { + ringbuf_put(&uart_ringbuf, RcvChar); + } + } + + // Clear pending FIFO interrupts + WRITE_PERI_REG(UART_INT_CLR(UART_REPL), UART_RXFIFO_TOUT_INT_CLR | UART_RXFIFO_FULL_INT_ST); + ETS_UART_INTR_ENABLE(); + + if (uart_attached_to_dupterm) { + mp_hal_signal_input(); + } + } +} + +// Waits at most timeout microseconds for at least 1 char to become ready for reading. +// Returns true if something available, false if not. +bool ICACHE_FLASH_ATTR uart_rx_wait(uint32_t timeout_us) { + uint32_t start = system_get_time(); + for (;;) { + if (uart_ringbuf.iget != uart_ringbuf.iput) { + return true; // have at least 1 char ready for reading + } + if (system_get_time() - start >= timeout_us) { + return false; // timeout + } + ets_event_poll(); + } +} + +int uart_rx_any(uint8 uart) { + if (uart_ringbuf.iget != uart_ringbuf.iput) { + return true; // have at least 1 char ready for reading + } + return false; +} + +int uart_tx_any_room(uint8 uart) { + uint32_t fifo_cnt = READ_PERI_REG(UART_STATUS(uart)) & (UART_TXFIFO_CNT << UART_TXFIFO_CNT_S); + if ((fifo_cnt >> UART_TXFIFO_CNT_S & UART_TXFIFO_CNT) >= 126) { + return false; + } + return true; +} + +// Returns char from the input buffer, else -1 if buffer is empty. +int uart_rx_char(void) { + return ringbuf_get(&uart_ringbuf); +} + +int uart_rx_one_char(uint8 uart_no) { + if (READ_PERI_REG(UART_STATUS(uart_no)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) { + return READ_PERI_REG(UART_FIFO(uart_no)) & 0xff; + } + return -1; +} + +/****************************************************************************** + * FunctionName : uart_init + * Description : user interface for init uart + * Parameters : UartBautRate uart0_br - uart0 bautrate + * UartBautRate uart1_br - uart1 bautrate + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR uart_init(UartBautRate uart0_br, UartBautRate uart1_br) { + // rom use 74880 baut_rate, here reinitialize + UartDev.baut_rate = uart0_br; + uart_config(UART0); + UartDev.baut_rate = uart1_br; + uart_config(UART1); + ETS_UART_INTR_ENABLE(); + + // install handler for "os" messages + os_install_putc1((void *)uart_os_write_char); +} + +void ICACHE_FLASH_ATTR uart_reattach() { + uart_init(UART_BIT_RATE_74880, UART_BIT_RATE_74880); +} + +void ICACHE_FLASH_ATTR uart_setup(uint8 uart) { + ETS_UART_INTR_DISABLE(); + uart_config(uart); + ETS_UART_INTR_ENABLE(); +} + +int ICACHE_FLASH_ATTR uart0_get_rxbuf_len(void) { + return uart_ringbuf.size; +} + +void ICACHE_FLASH_ATTR uart0_set_rxbuf(uint8 *buf, int len) { + ETS_UART_INTR_DISABLE(); + uart_ringbuf.buf = buf; + uart_ringbuf.size = len; + uart_ringbuf.iget = 0; + uart_ringbuf.iput = 0; + ETS_UART_INTR_ENABLE(); +} + +// Task-based UART interface + +#include "py/obj.h" +#include "shared/runtime/pyexec.h" + +#if MICROPY_REPL_EVENT_DRIVEN +void ICACHE_FLASH_ATTR uart_task_handler(os_event_t *evt) { + if (pyexec_repl_active) { + // TODO: Just returning here isn't exactly right. + // What really should be done is something like + // enquing delayed event to itself, for another + // chance to feed data to REPL. Otherwise, there + // can be situation when buffer has bunch of data, + // and sits unprocessed, because we consumed all + // processing signals like this. + return; + } + + int c, ret = 0; + while ((c = ringbuf_get(&stdin_ringbuf)) >= 0) { + if (c == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } + ret = pyexec_event_repl_process_char(c); + if (ret & PYEXEC_FORCED_EXIT) { + break; + } + } + + if (ret & PYEXEC_FORCED_EXIT) { + soft_reset(); + } +} + +void uart_task_init() { + system_os_task(uart_task_handler, UART_TASK_ID, uart_evt_queue, sizeof(uart_evt_queue) / sizeof(*uart_evt_queue)); +} +#endif diff --git a/ports/esp8266/uart.h b/ports/esp8266/uart.h new file mode 100644 index 0000000000..de0919bde0 --- /dev/null +++ b/ports/esp8266/uart.h @@ -0,0 +1,112 @@ +#ifndef MICROPY_INCLUDED_ESP8266_UART_H +#define MICROPY_INCLUDED_ESP8266_UART_H + +#include + +#define UART0 (0) +#define UART1 (1) + +#define UART0_STATIC_RXBUF_LEN (16) + +typedef enum { + UART_FIVE_BITS = 0x0, + UART_SIX_BITS = 0x1, + UART_SEVEN_BITS = 0x2, + UART_EIGHT_BITS = 0x3 +} UartBitsNum4Char; + +typedef enum { + UART_ONE_STOP_BIT = 0x1, + UART_ONE_HALF_STOP_BIT = 0x2, + UART_TWO_STOP_BIT = 0x3 +} UartStopBitsNum; + +typedef enum { + UART_NONE_BITS = 0, + UART_ODD_BITS = BIT0, + UART_EVEN_BITS = 0 +} UartParityMode; + +typedef enum { + UART_STICK_PARITY_DIS = 0, + UART_STICK_PARITY_EN = BIT1 +} UartExistParity; + +typedef enum { + UART_BIT_RATE_9600 = 9600, + UART_BIT_RATE_19200 = 19200, + UART_BIT_RATE_38400 = 38400, + UART_BIT_RATE_57600 = 57600, + UART_BIT_RATE_74880 = 74880, + UART_BIT_RATE_115200 = 115200, + UART_BIT_RATE_230400 = 230400, + UART_BIT_RATE_256000 = 256000, + UART_BIT_RATE_460800 = 460800, + UART_BIT_RATE_921600 = 921600 +} UartBautRate; + +typedef enum { + UART_NONE_CTRL, + UART_HARDWARE_CTRL, + UART_XON_XOFF_CTRL +} UartFlowCtrl; + +typedef enum { + UART_EMPTY, + UART_UNDER_WRITE, + UART_WRITE_OVER +} RcvMsgBuffState; + +typedef struct { + uint32 RcvBuffSize; + uint8 *pRcvMsgBuff; + uint8 *pWritePos; + uint8 *pReadPos; + uint8 TrigLvl; // JLU: may need to pad + RcvMsgBuffState BuffState; +} RcvMsgBuff; + +typedef struct { + uint32 TrxBuffSize; + uint8 *pTrxBuff; +} TrxMsgBuff; + +typedef enum { + UART_BAUD_RATE_DET, + UART_WAIT_SYNC_FRM, + UART_SRCH_MSG_HEAD, + UART_RCV_MSG_BODY, + UART_RCV_ESC_CHAR, +} RcvMsgState; + +typedef struct { + UartBautRate baut_rate; + UartBitsNum4Char data_bits; + UartExistParity exist_parity; + UartParityMode parity; // chip size in byte + UartStopBitsNum stop_bits; + UartFlowCtrl flow_ctrl; + RcvMsgBuff rcv_buff; + TrxMsgBuff trx_buff; + RcvMsgState rcv_state; + int received; + int buff_uart_no; // indicate which uart use tx/rx buffer +} UartDevice; + +extern uint8 uart_ringbuf_array[UART0_STATIC_RXBUF_LEN]; + +void uart_init(UartBautRate uart0_br, UartBautRate uart1_br); +int uart0_rx(void); +bool uart_rx_wait(uint32_t timeout_us); +int uart_rx_char(void); +void uart_tx_one_char(uint8 uart, uint8 TxChar); +void uart_flush(uint8 uart); +void uart_os_config(int uart); +void uart_setup(uint8 uart); +int uart0_get_rxbuf_len(void); +void uart0_set_rxbuf(uint8 *buf, int len); +// check status of rx/tx +int uart_rx_any(uint8 uart); +int uart_tx_any_room(uint8 uart); + +#endif // MICROPY_INCLUDED_ESP8266_UART_H diff --git a/ports/esp8266/uart_register.h b/ports/esp8266/uart_register.h new file mode 100644 index 0000000000..f0d646a525 --- /dev/null +++ b/ports/esp8266/uart_register.h @@ -0,0 +1,128 @@ +// Generated at 2012-07-03 18:44:06 +/* + * Copyright (c) 2010 - 2011 Espressif System + * + */ + +#ifndef UART_REGISTER_H_INCLUDED +#define UART_REGISTER_H_INCLUDED +#define REG_UART_BASE(i) (0x60000000 + (i) * 0xf00) +// version value:32'h062000 + +#define UART_FIFO(i) (REG_UART_BASE(i) + 0x0) +#define UART_RXFIFO_RD_BYTE 0x000000FF +#define UART_RXFIFO_RD_BYTE_S 0 + +#define UART_INT_RAW(i) (REG_UART_BASE(i) + 0x4) +#define UART_RXFIFO_TOUT_INT_RAW (BIT(8)) +#define UART_BRK_DET_INT_RAW (BIT(7)) +#define UART_CTS_CHG_INT_RAW (BIT(6)) +#define UART_DSR_CHG_INT_RAW (BIT(5)) +#define UART_RXFIFO_OVF_INT_RAW (BIT(4)) +#define UART_FRM_ERR_INT_RAW (BIT(3)) +#define UART_PARITY_ERR_INT_RAW (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_RAW (BIT(1)) +#define UART_RXFIFO_FULL_INT_RAW (BIT(0)) + +#define UART_INT_ST(i) (REG_UART_BASE(i) + 0x8) +#define UART_RXFIFO_TOUT_INT_ST (BIT(8)) +#define UART_BRK_DET_INT_ST (BIT(7)) +#define UART_CTS_CHG_INT_ST (BIT(6)) +#define UART_DSR_CHG_INT_ST (BIT(5)) +#define UART_RXFIFO_OVF_INT_ST (BIT(4)) +#define UART_FRM_ERR_INT_ST (BIT(3)) +#define UART_PARITY_ERR_INT_ST (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_ST (BIT(1)) +#define UART_RXFIFO_FULL_INT_ST (BIT(0)) + +#define UART_INT_ENA(i) (REG_UART_BASE(i) + 0xC) +#define UART_RXFIFO_TOUT_INT_ENA (BIT(8)) +#define UART_BRK_DET_INT_ENA (BIT(7)) +#define UART_CTS_CHG_INT_ENA (BIT(6)) +#define UART_DSR_CHG_INT_ENA (BIT(5)) +#define UART_RXFIFO_OVF_INT_ENA (BIT(4)) +#define UART_FRM_ERR_INT_ENA (BIT(3)) +#define UART_PARITY_ERR_INT_ENA (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_ENA (BIT(1)) +#define UART_RXFIFO_FULL_INT_ENA (BIT(0)) + +#define UART_INT_CLR(i) (REG_UART_BASE(i) + 0x10) +#define UART_RXFIFO_TOUT_INT_CLR (BIT(8)) +#define UART_BRK_DET_INT_CLR (BIT(7)) +#define UART_CTS_CHG_INT_CLR (BIT(6)) +#define UART_DSR_CHG_INT_CLR (BIT(5)) +#define UART_RXFIFO_OVF_INT_CLR (BIT(4)) +#define UART_FRM_ERR_INT_CLR (BIT(3)) +#define UART_PARITY_ERR_INT_CLR (BIT(2)) +#define UART_TXFIFO_EMPTY_INT_CLR (BIT(1)) +#define UART_RXFIFO_FULL_INT_CLR (BIT(0)) + +#define UART_CLKDIV(i) (REG_UART_BASE(i) + 0x14) +#define UART_CLKDIV_CNT 0x000FFFFF +#define UART_CLKDIV_S 0 + +#define UART_AUTOBAUD(i) (REG_UART_BASE(i) + 0x18) +#define UART_GLITCH_FILT 0x000000FF +#define UART_GLITCH_FILT_S 8 +#define UART_AUTOBAUD_EN (BIT(0)) + +#define UART_STATUS(i) (REG_UART_BASE(i) + 0x1C) +#define UART_TXD (BIT(31)) +#define UART_RTSN (BIT(30)) +#define UART_DTRN (BIT(29)) +#define UART_TXFIFO_CNT 0x000000FF +#define UART_TXFIFO_CNT_S 16 +#define UART_RXD (BIT(15)) +#define UART_CTSN (BIT(14)) +#define UART_DSRN (BIT(13)) +#define UART_RXFIFO_CNT 0x000000FF +#define UART_RXFIFO_CNT_S 0 + +#define UART_CONF0(i) (REG_UART_BASE(i) + 0x20) +#define UART_TXFIFO_RST (BIT(18)) +#define UART_RXFIFO_RST (BIT(17)) +#define UART_IRDA_EN (BIT(16)) +#define UART_TX_FLOW_EN (BIT(15)) +#define UART_LOOPBACK (BIT(14)) +#define UART_IRDA_RX_INV (BIT(13)) +#define UART_IRDA_TX_INV (BIT(12)) +#define UART_IRDA_WCTL (BIT(11)) +#define UART_IRDA_TX_EN (BIT(10)) +#define UART_IRDA_DPLX (BIT(9)) +#define UART_TXD_BRK (BIT(8)) +#define UART_SW_DTR (BIT(7)) +#define UART_SW_RTS (BIT(6)) +#define UART_STOP_BIT_NUM 0x00000003 +#define UART_STOP_BIT_NUM_S 4 +#define UART_BIT_NUM 0x00000003 +#define UART_BIT_NUM_S 2 +#define UART_PARITY_EN (BIT(1)) +#define UART_PARITY (BIT(0)) + +#define UART_CONF1(i) (REG_UART_BASE(i) + 0x24) +#define UART_RX_TOUT_EN (BIT(31)) +#define UART_RX_TOUT_THRHD 0x0000007F +#define UART_RX_TOUT_THRHD_S 24 +#define UART_RX_FLOW_EN (BIT(23)) +#define UART_RX_FLOW_THRHD 0x0000007F +#define UART_RX_FLOW_THRHD_S 16 +#define UART_TXFIFO_EMPTY_THRHD 0x0000007F +#define UART_TXFIFO_EMPTY_THRHD_S 8 +#define UART_RXFIFO_FULL_THRHD 0x0000007F +#define UART_RXFIFO_FULL_THRHD_S 0 + +#define UART_LOWPULSE(i) (REG_UART_BASE(i) + 0x28) +#define UART_LOWPULSE_MIN_CNT 0x000FFFFF +#define UART_LOWPULSE_MIN_CNT_S 0 + +#define UART_HIGHPULSE(i) (REG_UART_BASE(i) + 0x2C) +#define UART_HIGHPULSE_MIN_CNT 0x000FFFFF +#define UART_HIGHPULSE_MIN_CNT_S 0 + +#define UART_PULSE_NUM(i) (REG_UART_BASE(i) + 0x30) +#define UART_PULSE_NUM_CNT 0x0003FF +#define UART_PULSE_NUM_CNT_S 0 + +#define UART_DATE(i) (REG_UART_BASE(i) + 0x78) +#define UART_ID(i) (REG_UART_BASE(i) + 0x7C) +#endif // UART_REGISTER_H_INCLUDED diff --git a/ports/esp8266/user_config.h b/ports/esp8266/user_config.h new file mode 100644 index 0000000000..8b1a393741 --- /dev/null +++ b/ports/esp8266/user_config.h @@ -0,0 +1 @@ +// empty diff --git a/ports/esp8266/xtirq.h b/ports/esp8266/xtirq.h new file mode 100644 index 0000000000..835f06bcae --- /dev/null +++ b/ports/esp8266/xtirq.h @@ -0,0 +1,59 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP8266_XTIRQ_H +#define MICROPY_INCLUDED_ESP8266_XTIRQ_H + +#include + +// returns the value of "intlevel" from the PS register +static inline uint32_t query_irq(void) { + uint32_t ps; + __asm__ volatile ("rsr %0, ps" : "=a" (ps)); + return ps & 0xf; +} + +// irqs with a priority value lower or equal to "intlevel" will be disabled +// "intlevel" should be between 0 and 15 inclusive, and should be an integer +static inline uint32_t raise_irq_pri(uint32_t intlevel) { + uint32_t old_ps; + __asm__ volatile ("rsil %0, %1" : "=a" (old_ps) : "I" (intlevel)); + return old_ps; +} + +// "ps" should be the value returned from raise_irq_pri +static inline void restore_irq_pri(uint32_t ps) { + __asm__ volatile ("wsr %0, ps; rsync" : : "a" (ps)); +} + +static inline uint32_t disable_irq(void) { + return raise_irq_pri(15); +} + +static inline void enable_irq(uint32_t irq_state) { + restore_irq_pri(irq_state); +} + +#endif // MICROPY_INCLUDED_ESP8266_XTIRQ_H diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 84f23af23c..6da7f7ec85 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -206,9 +206,9 @@ uint32_t rosc_random_u32(void) { } const char rp2_help_text[] = - "Welcome to MicroPython!\n" + "Welcome to HaaS Python!\n" "\n" - "For online help please visit https://micropython.org/help/.\n" + "For online help please visit https://haas.iot.aliyun.com/.\n" "\n" "For access to the hardware use the 'machine' module. RP2 specific commands\n" "are in the 'rp2' module.\n" diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 71fbe46421..c9f6afab0e 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -23,7 +23,7 @@ SRCDIRS_HAAS := SRC_HAAS := # Set AOS lib path -AOS_DIR := $(TOP)/../.. +AOS_DIR := $(MPY_DIR)/../.. # Set external dir EXTERNAL_DIR ?= $(TOP)/external @@ -381,6 +381,8 @@ SRC_C += \ servo.c \ dac.c \ adc.c \ + modaudio.c \ + wm8978.c \ $(wildcard $(BOARD_DIR)/*.c) SRC_CXX += \ @@ -830,3 +832,10 @@ $(GEN_CDCINF_FILE): $(CDCINF_TEMPLATE) $(INSERT_USB_IDS) $(USB_IDS_FILE) | $(HEA $(Q)$(PYTHON) $(INSERT_USB_IDS) $(USB_IDS_FILE) $< > $@ include $(TOP)/py/mkrules.mk + + +PYTHON = python3 +MAKEFS = make-fs.py +fs: + @echo "Generating File System." + $(PYTHON) $(MAKEFS) diff --git a/ports/stm32/boardctrl.c b/ports/stm32/boardctrl.c index 922f218e92..0b65dbd79d 100644 --- a/ports/stm32/boardctrl.c +++ b/ports/stm32/boardctrl.c @@ -184,7 +184,7 @@ int boardctrl_run_main_py(boardctrl_state_t *state) { // Run main.py (or what it was configured to be), if it exists. const char *main_py; if (MP_STATE_PORT(pyb_config_main) == MP_OBJ_NULL) { - main_py = "main.py"; + main_py = "/data/pyamp/main.py"; } else { main_py = mp_obj_str_get_str(MP_STATE_PORT(pyb_config_main)); } diff --git a/ports/stm32/boards/COLUMBUS/manifest.py b/ports/stm32/boards/COLUMBUS/manifest.py index 4a4d85258e..9080203813 100644 --- a/ports/stm32/boards/COLUMBUS/manifest.py +++ b/ports/stm32/boards/COLUMBUS/manifest.py @@ -19,12 +19,17 @@ # ulinksdk include("$(MPY_DIR)/modules/ulinksdk/manifest.py") +# aliyunIoT +freeze("$(MPY_DIR)/modules/ualiyunIoT", "aliyunIoT.py") + # http freeze("$(MPY_DIR)/modules/http", "http.py") +# ukv +freeze("$(MPY_DIR)/modules/ukv", "kv.py") + # oss include("$(MPY_DIR)/modules/oss/manifest.py") # ukv -freeze("$(MPY_DIR)/modules/ukv", ("kv.py", "systemAdaptor.py")) - +freeze("$(MPY_DIR)/modules/adaptor/stm32", "systemAdaptor.py") diff --git a/ports/stm32/boards/COLUMBUS/manifest_release.py b/ports/stm32/boards/COLUMBUS/manifest_release.py index e1a89edcf1..bf2d00ce72 100644 --- a/ports/stm32/boards/COLUMBUS/manifest_release.py +++ b/ports/stm32/boards/COLUMBUS/manifest_release.py @@ -5,3 +5,5 @@ # freeze("$(MPY_LIB_DIR)/micropython/upysh", "upysh.py") freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple.py") freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust.py") +freeze("$(MPY_DIR)/modules/umqtt.simple", "umqtt/simple2.py") +freeze("$(MPY_DIR)/modules/umqtt.robust", "umqtt/robust2.py") diff --git a/ports/stm32/boards/COLUMBUS/mpconfigboard.h b/ports/stm32/boards/COLUMBUS/mpconfigboard.h index 24628b93d6..d9bd2dee35 100644 --- a/ports/stm32/boards/COLUMBUS/mpconfigboard.h +++ b/ports/stm32/boards/COLUMBUS/mpconfigboard.h @@ -1,6 +1,7 @@ #define MICROPY_HW_BOARD_NAME "01Studio Columbus" #define MICROPY_HW_MCU_NAME "STM32F407ZGT6" -#define MICROPY_HW_FLASH_FS_LABEL "COLUMBUS" +#define MICROPY_HW_FLASH_FS_LABEL "COLUMBUS" +#define MICROPY_SW_VENDOR_NAME "HaaSPython" #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) #define MICROPY_HW_HAS_SWITCH (1) @@ -37,9 +38,9 @@ //------------------------------------------------------------------------------------ //audio -#define MICROPY_ENABLE_AUDIO (0) -#define MICROPY_HW_WM8978 (0) -#define MICROPY_ENABLE_MP3 (0) +#define MICROPY_ENABLE_AUDIO (1) +#define MICROPY_HW_WM8978 (1) +#define MICROPY_ENABLE_MP3 (0) //------------------------------------------------------------------------------------ //video #define MICROPY_ENABLE_VIDEO (1) @@ -160,7 +161,7 @@ extern struct _spi_bdev_t spi_bdev; #define MICROPY_HW_ETH_RMII_TXD0 (pin_G13) #define MICROPY_HW_ETH_RMII_TXD1 (pin_G14) -#define MICROPY_PY_NETWORK_LAN8720 (1) +#define MICROPY_PY_NETWORK_LAN8720 (0) // SD card detect switch #define MICROPY_HW_ENABLE_SDCARD (1) diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c index b376ee23b5..ac1cf3d962 100644 --- a/ports/stm32/dma.c +++ b/ports/stm32/dma.c @@ -33,6 +33,9 @@ #include "systick.h" #include "dma.h" #include "irq.h" +#if MICROPY_HW_WM8978 +#include "wm8978.h" +#endif #define DMA_IDLE_ENABLED() (dma_idle.enabled != 0) #define DMA_SYSTICK_LOG2 (3) @@ -652,6 +655,12 @@ void DMA1_Stream2_IRQHandler(void) { } void DMA1_Stream3_IRQHandler(void) { IRQ_ENTER(DMA1_Stream3_IRQn); +#if MICROPY_HW_WM8978 + if (__HAL_DMA_GET_FLAG(&I2S2_RXDMA_Handler, DMA_FLAG_TCIF3_7) != RESET) { + __HAL_DMA_CLEAR_FLAG(&I2S2_RXDMA_Handler, DMA_FLAG_TCIF3_7); + i2s_rx_callback(); + } +#endif if (dma_handle[dma_id_3] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_3]); } @@ -659,6 +668,15 @@ void DMA1_Stream3_IRQHandler(void) { } void DMA1_Stream4_IRQHandler(void) { IRQ_ENTER(DMA1_Stream4_IRQn); +#if MICROPY_HW_WM8978 + if (__HAL_DMA_GET_FLAG(&I2S2_TXDMA_Handler, DMA_FLAG_TCIF0_4) != RESET) { + __HAL_DMA_CLEAR_FLAG(&I2S2_TXDMA_Handler, DMA_FLAG_TCIF0_4); + i2s_tx_callback(); +#if defined(STM32F7) + SCB_CleanInvalidateDCache(); +#endif + } +#endif if (dma_handle[dma_id_4] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_4]); } diff --git a/ports/stm32/eth.h b/ports/stm32/eth.h index 0b75d95875..d901e26760 100644 --- a/ports/stm32/eth.h +++ b/ports/stm32/eth.h @@ -32,10 +32,10 @@ #define PHY_READ_TO ((uint32_t)0x0004FFFF) #define PHY_WRITE_TO ((uint32_t)0x0004FFFF) -#if defined(MICROPY_PY_NETWORK_LAN8720) || defined(MICROPY_PY_NETWORK_LAN8742) +#if MICROPY_PY_NETWORK_LAN8720 || MICROPY_PY_NETWORK_LAN8742 #define ETH_PHY_ADDR (0x01) #else -#define ETH_PHY_ADDR (0x01) +#define ETH_PHY_ADDR (0x00) #endif typedef struct _eth_t eth_t; diff --git a/ports/stm32/global.h b/ports/stm32/global.h new file mode 100644 index 0000000000..8dd00f0018 --- /dev/null +++ b/ports/stm32/global.h @@ -0,0 +1,375 @@ +#ifndef __GLOBAL_H +#define __GLOBAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#include "systick.h" +#include "pendsv.h" + +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" + +extern mp_obj_t get_path(const char *src_path, uint8_t *res); +extern mp_obj_t file_type(const char *fileName); +extern FRESULT f_open_helper(FIL *fp, const TCHAR *path, BYTE mode); +extern int sprintf(char *str, const char *fmt, ...); +extern bool check_sys_file(const char *check_file); + +#define my_isdigit(c) ((c) >= '0' && (c) <= '9') + +#ifdef __cplusplus +} +#endif +#endif + +#ifdef __GLOBAL_H +// ----------------------------------------------------------------------- +__attribute__((weak)) bool check_sys_file(const char *check_file) +{ + mp_obj_t iter = mp_vfs_ilistdir(0, NULL); + mp_obj_t next; + while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + const char *flie = mp_obj_str_get_str(mp_obj_subscr(next, MP_OBJ_NEW_SMALL_INT(0), MP_OBJ_SENTINEL)); + if (0 == strcmp(check_file, flie)) { + return true; + } + } + return false; +} + +__weak char *itoa(int num, char *str, int radix) +{ + char index[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + unsigned unum; + int i = 0, j, k; + + if (radix == 10 && num < 0) { + unum = (unsigned)-num; + str[i++] = '-'; + } else { + unum = (unsigned)num; + } + do { + str[i++] = index[unum % (unsigned)radix]; + unum /= radix; + + } while (unum); + str[i] = '\0'; + if (str[0] == '-') + k = 1; + else + k = 0; + + char temp; + for (j = k; j <= (i - 1) / 2; j++) { + temp = str[j]; + str[j] = str[i - 1 + k - j]; + str[i - 1 + k - j] = temp; + } + return str; +} + +__weak int atoi(const char *pstr) +{ + int Ret_Integer = 0; + int Integer_sign = 1; + /* + * 判断指针是否为空 + */ + if (pstr == NULL) { + return 0; + } + + /* + * 跳过前面的空格字符 + */ + while (*pstr == '\0') { + pstr++; + } + + /* + * 判断正负号 + * 如果是正号,指针指向下一个字符 + * 如果是符号,把符号标记为Integer_sign置-1,然后再把指针指向下一个字符 + */ + if (*pstr == '-') { + Integer_sign = -1; + } + if (*pstr == '-' || *pstr == '+') { + pstr++; + } + + /* + * 把数字字符串逐个转换成整数,并把最后转换好的整数赋给Ret_Integer + */ + while (*pstr >= '0' && *pstr <= '9') { + Ret_Integer = Ret_Integer * 10 + *pstr - '0'; + pstr++; + } + Ret_Integer = Integer_sign * Ret_Integer; + + return Ret_Integer; +} +__weak char *strrchr(const char *str, int ch) +{ + char *p = (char *)str; + while (*str) + str++; + while (str-- != p && *str != (char)ch) + ; + if (*str == (char)ch) + return (char *)str; + return NULL; +} + +// ----------------------------------------------------------------------- +// 路径解析出来 +// 返回0 为flash 1为sd。否则解析失败。 +// src_path:输入路径 ret_path:返回的相对路径 +__weak mp_obj_t get_path(const char *src_path, uint8_t *res) +{ + uint8_t date_len = 0, cp_len = 0; + + char upda_str[50]; + + char cp_str[7]; + char *ret_path; + + memset(upda_str, '\0', sizeof(upda_str)); + strncpy(upda_str, &src_path[1], strlen(src_path) - 1); + date_len = strlen(upda_str); + + ret_path = strchr(upda_str, '/'); + if (ret_path == 0) + mp_raise_ValueError(MP_ERROR_TEXT("file path error")); + + cp_len = date_len - strlen(ret_path); + + memset(cp_str, '\0', 7); + strncpy(cp_str, upda_str, cp_len); + + *res = 2; + if (strncmp(cp_str, "flash", 5) == 0) + *res = 0; + else if (strncmp(cp_str, "sd", 2) == 0) + *res = 1; + else { + mp_raise_ValueError(MP_ERROR_TEXT("no find sd or flash path")); + } + + date_len = (date_len - cp_len - 1); + memset(upda_str, '\0', sizeof(upda_str)); + strncpy(upda_str, &src_path[cp_len + 2], date_len); + + return mp_obj_new_str(upda_str, date_len); +} + +__weak mp_obj_t file_type(const char *fileName) +{ + char dest[10]; + memset(dest, '\0', sizeof(dest)); + char *ret = strrchr(fileName, '.'); + if (ret == NULL) { + mp_raise_TypeError(MP_ERROR_TEXT("no find file type")); + } + strncpy(dest, &ret[1], strlen(ret) - 1); + return mp_obj_new_str(dest, strlen(ret) - 1); +} +__weak int sprintf(char *str, const char *fmt, ...) +{ + int count = 0; + char c; + char *s; + int n; + + int index = 0; + int ret = 2; + + char buf[65]; + char digit[16]; + int num = 0; + int len = 0; + + memset(buf, 0, sizeof(buf)); + memset(digit, 0, sizeof(digit)); + + va_list ap; + + va_start(ap, fmt); + + while (*fmt != '\0') { + // printf("*fmt=[%c]\n", *fmt); + if (*fmt == '%') { + fmt++; + switch (*fmt) { + case 'd': /* 整型 */ + { + n = va_arg(ap, int); + if (n < 0) { + *str = '-'; + str++; + n = -n; + } + // printf("case d n=[%d]\n", n); + // itoa(n, buf); + itoa(n, buf, 10); + // printf("case d buf=[%s]\n", buf); + memcpy(str, buf, strlen(buf)); + str += strlen(buf); + break; + } + case 'c': /* 字符型 */ + { + c = va_arg(ap, int); + *str = c; + str++; + + break; + } + case 'x': /* 16进制 */ + { + n = va_arg(ap, int); + // xtoa(n, buf); + itoa(n, buf, 16); + memcpy(str, buf, strlen(buf)); + str += strlen(buf); + break; + } + case 's': /* 字符串 */ + { + s = va_arg(ap, char *); + memcpy(str, s, strlen(s)); + str += strlen(s); + break; + } + case '%': /* 输出% */ + { + *str = '%'; + str++; + + break; + } + case '0': /* 位不足的左补0 */ + { + index = 0; + num = 0; + memset(digit, 0, sizeof(digit)); + + while (1) { + fmt++; + ret = my_isdigit(*fmt); + if (ret == 1) { + // 是数字 + digit[index] = *fmt; + index++; + } else { + num = atoi(digit); + break; + } + } + switch (*fmt) { + case 'd': /* 整型 */ + { + n = va_arg(ap, int); + if (n < 0) { + *str = '-'; + str++; + n = -n; + } + // itoa(n, buf); + itoa(n, buf, 10); + len = strlen(buf); + if (len >= num) { + memcpy(str, buf, strlen(buf)); + str += strlen(buf); + } else { + memset(str, '0', num - len); + str += num - len; + memcpy(str, buf, strlen(buf)); + str += strlen(buf); + } + break; + } + case 'x': /* 16进制 */ + { + n = va_arg(ap, int); + // xtoa(n, buf); + itoa(n, buf, 16); + len = strlen(buf); + if (len >= num) { + memcpy(str, buf, len); + str += len; + } else { + memset(str, '0', num - len); + str += num - len; + memcpy(str, buf, len); + str += len; + } + break; + } + case 's': /* 字符串 */ + { + s = va_arg(ap, char *); + len = strlen(s); + if (len >= num) { + memcpy(str, s, strlen(s)); + str += strlen(s); + } else { + memset(str, '0', num - len); + str += num - len; + memcpy(str, s, strlen(s)); + str += strlen(s); + } + break; + } + default: + break; + } + } + default: + break; + } + } else { + *str = *fmt; + str++; + + if (*fmt == '\n') { + } + } + fmt++; + } + + va_end(ap); + + return count; +} +// ================================================================== + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +STATIC FATFS *lookup_path(const TCHAR **path) +{ + mp_vfs_mount_t *fs = mp_vfs_lookup_path(*path, path); + if (fs == MP_VFS_NONE || fs == MP_VFS_ROOT) { + return NULL; + } + // here we assume that the mounted device is FATFS + return &((fs_user_mount_t *)MP_OBJ_TO_PTR(fs->obj))->fatfs; +} + +__weak FRESULT f_open_helper(FIL *fp, const TCHAR *path, BYTE mode) +{ + FATFS *fs = lookup_path(&path); + if (fs == NULL) { + return FR_NO_PATH; + } + return f_open(fs, fp, path, mode); +} +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +#endif diff --git a/ports/stm32/help.c b/ports/stm32/help.c index fba0a6937f..4169501d4c 100644 --- a/ports/stm32/help.c +++ b/ports/stm32/help.c @@ -27,9 +27,9 @@ #include "py/builtin.h" const char stm32_help_text[] = - "Welcome to MicroPython!\n" + "Welcome to HaaS Python!\n" "\n" - "For online help please visit http://micropython.org/help/.\n" + "For online help please visit https://haas.iot.aliyun.com/.\n" "\n" "Quick overview of commands for the board:\n" " pyb.info() -- print some general information\n" diff --git a/ports/stm32/make-fs.py b/ports/stm32/make-fs.py new file mode 100644 index 0000000000..570ca120f3 --- /dev/null +++ b/ports/stm32/make-fs.py @@ -0,0 +1,22 @@ +# Combine bootloader, partition table and application into a final binary. + +import os, sys +import platform + +cur_os = platform.system() +arch = platform.architecture() + +max_size_data = 1024 * 128 +littlefs_bin = 'littlefs.bin' + +if cur_os == 'Linux': + mklittlefs_tool = './mklittlefs_linux' +elif cur_os == 'Darwin': + mklittlefs_tool = './mklittlefs_osx' +elif cur_os == 'Windows': + mklittlefs_tool = './mklittlefs.exe' + +target_path = os.path.join("./", "file-system") +cmd_str = "\"%s\" -c \"%s\" -d 5 -b 4096 -p 256 -s %d \"%s\"" % (mklittlefs_tool, target_path, max_size_data, littlefs_bin) +os.system(cmd_str) + diff --git a/ports/stm32/mklittlefs.exe b/ports/stm32/mklittlefs.exe new file mode 100644 index 0000000000000000000000000000000000000000..387d9838608538310fbe6c7dcb1b6723df732f73 GIT binary patch literal 371189 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 UeN~V7;}I`F+y9&XU!lPN17Q!zEdT%j literal 0 HcmV?d00001 diff --git a/ports/stm32/mklittlefs_linux b/ports/stm32/mklittlefs_linux new file mode 100755 index 0000000000000000000000000000000000000000..969c2ed203feb91e314d6ddcfd2e5646475e8d98 GIT binary patch literal 100536 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 LmDS^_tLT3L8bNex literal 0 HcmV?d00001 diff --git a/ports/stm32/modaudio.c b/ports/stm32/modaudio.c new file mode 100644 index 0000000000..fbb4b0b5ba --- /dev/null +++ b/ports/stm32/modaudio.c @@ -0,0 +1,47 @@ +/** + ****************************************************************************** + * This file is part of the MicroPython project, http://bbs.01studio.org/ + * Copyright (C), 2020 -2021, 01studio Tech. Co., Ltd. + * File Name : + * Author : + * Version : + * date : + * Description : + ****************************************************************************** +**/ + +#include +#include +#include + +#include "py/objlist.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "shared/netutils/netutils.h" +#include "systick.h" +#include "pendsv.h" +#include "portmodules.h" + +#if MICROPY_ENABLE_AUDIO + +#if MICROPY_HW_WM8978 +#include "wm8978.h" +#endif + + +STATIC const mp_rom_map_elem_t audio_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audio) }, +#if MICROPY_HW_WM8978 + { MP_ROM_QSTR(MP_QSTR_WM8978), MP_ROM_PTR(&audio_wm8978_type) }, +#endif +}; +STATIC MP_DEFINE_CONST_DICT(audio_module_globals, audio_module_globals_table); + +const mp_obj_module_t audio_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&audio_module_globals, +}; + +/*******************************************************************************/ + +#endif // MICROPY_ENABLE_AUDIO diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index ebc84a71af..e25d0711f7 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -35,6 +35,9 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) #endif +// Memory allocation policies +#define MICROPY_QSTR_BYTES_IN_HASH (1) + // memory allocation policies #ifndef MICROPY_GC_STACK_ENTRY_TYPE #if MICROPY_HW_SDRAM_SIZE @@ -92,6 +95,7 @@ #endif // extended modules +#define MICROPY_EPOCH_IS_1970 (1) #define MICROPY_PY_USSL_FINALISER (MICROPY_PY_USSL) #define MICROPY_PY_UHASHLIB_MD5 (MICROPY_PY_USSL) #define MICROPY_PY_UHASHLIB_SHA1 (MICROPY_PY_USSL) @@ -141,6 +145,10 @@ #define MICROPY_PY_UPLATFORM (1) #endif +#ifndef MICROPY_ENABLE_AUDIO +#define MICROPY_ENABLE_AUDIO (0) +#endif + // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ @@ -184,6 +192,7 @@ extern const struct _mp_obj_module_t mp_module_utime; extern const struct _mp_obj_module_t mp_module_usocket; extern const struct _mp_obj_module_t mp_module_network; extern const struct _mp_obj_module_t mp_module_onewire; +extern const struct _mp_obj_module_t audio_module; #if MICROPY_PY_PYB #define PYB_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_pyb), MP_ROM_PTR(&pyb_module) }, @@ -240,6 +249,12 @@ extern const struct _mp_obj_module_t mp_module_onewire; #define ONEWIRE_BUILTIN_MODULE #endif +#if MICROPY_ENABLE_AUDIO +#define AUDIO_MODULE { MP_ROM_QSTR(MP_QSTR_audio), MP_ROM_PTR(&audio_module) }, +#else +#define AUDIO_MODULE +#endif + #if defined(MICROPY_HW_ETH_MDC) extern const struct _mp_obj_type_t network_lan_type; #define MICROPY_HW_NIC_ETH { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&network_lan_type) }, @@ -281,6 +296,7 @@ extern const struct _mod_network_nic_type_t mod_network_nic_type_cc3k; SOCKET_BUILTIN_MODULE \ NETWORK_BUILTIN_MODULE \ ONEWIRE_BUILTIN_MODULE \ + AUDIO_MODULE \ // extra constants #define MICROPY_PORT_CONSTANTS \ @@ -288,6 +304,7 @@ extern const struct _mod_network_nic_type_t mod_network_nic_type_cc3k; MACHINE_BUILTIN_MODULE_CONSTANTS \ PYB_BUILTIN_MODULE \ STM_BUILTIN_MODULE \ + AUDIO_MODULE \ #define MICROPY_PORT_NETWORK_INTERFACES \ MICROPY_HW_NIC_ETH \ diff --git a/py/builtinhelp.c b/py/builtinhelp.c index 84d69caf35..544d105e1e 100644 --- a/py/builtinhelp.c +++ b/py/builtinhelp.c @@ -33,9 +33,9 @@ #if MICROPY_PY_BUILTINS_HELP const char mp_help_default_text[] = - "Welcome to MicroPython!\n" + "Welcome to HaaS Python!\n" "\n" - "For online docs please visit http://docs.micropython.org/\n" + "For online docs please visit https://haas.iot.aliyun.com/.\n" "\n" "Control commands:\n" " CTRL-A -- on a blank line, enter raw REPL mode\n" diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 54b7fa9ab7..95cbf6373d 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -75,6 +75,12 @@ def get_version_info_from_docs_conf(): def make_version_header(filename): # Get version info using git, with fallback to docs/conf.py info = get_version_info_from_git() + + # HaaS Python modification begin + # get info from docs_conf + info = None + # HaaS Python modification end + if info is None: info = get_version_info_from_docs_conf() diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 5f05c1da3e..34c9e1e2d1 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -397,7 +397,7 @@ STATIC int pyexec_friendly_repl_process_char(int c) { } else if (ret == CHAR_CTRL_B) { // reset friendly REPL mp_hal_stdout_tx_str("\r\n"); - mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); + mp_hal_stdout_tx_str("HaaS Python " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); #if MICROPY_PY_BUILTINS_HELP mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); #endif @@ -548,7 +548,7 @@ int pyexec_friendly_repl(void) { vstr_init(&line, 32); friendly_repl_reset: - mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); + mp_hal_stdout_tx_str("HaaS Python " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); #if MICROPY_PY_BUILTINS_HELP mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); #endif