diff --git a/.github/workflows/build_esp32c3m.yml b/.github/workflows/build_esp32c3m.yml new file mode 100644 index 00000000..ed15b79b --- /dev/null +++ b/.github/workflows/build_esp32c3m.yml @@ -0,0 +1,71 @@ +name: Build ESP32C3M Firmware + +on: + push: + branches: [ ver4dev ] + paths: + - 'platformio.ini' + - 'src/**' + - 'include/**' + - 'lib/**' + - 'data_*/**' + - 'bin/**' + - 'tools/**' + - 'PrepareProject.py' + - '.github/workflows/build_esp32c3m.yml' + pull_request: + branches: [ ver4dev ] + paths: + - 'platformio.ini' + - 'src/**' + - 'include/**' + - 'lib/**' + - 'data_*/**' + - 'bin/**' + - 'tools/**' + - 'PrepareProject.py' + - '.github/workflows/build_esp32c3m.yml' + workflow_dispatch: + +env: + BOARD: esp32c3m_4mb + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install platformio + + - name: Prepare project + run: python ./PrepareProject.py -b ${BOARD} + + - name: Build firmware + run: platformio run -e ${BOARD} + + - name: Build filesystem image + run: platformio run -e ${BOARD} -t buildfs --disable-auto-clean + + - name: Collect artifacts + run: | + mkdir -p artifacts/${BOARD} + find .pio/build/${BOARD} -maxdepth 1 -type f -name '*.bin' -exec cp {} artifacts/${BOARD} \; + if [ -f build_${BOARD}.log ]; then cp build_${BOARD}.log artifacts/${BOARD}/; fi + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${BOARD}-firmware-${{ github.run_number }} + path: artifacts/${BOARD} diff --git a/.github/workflows/build_iotm.yml b/.github/workflows/build_iotm.yml new file mode 100644 index 00000000..2cef098a --- /dev/null +++ b/.github/workflows/build_iotm.yml @@ -0,0 +1,52 @@ +env: + BOARDS: '["esp8266_4mb", "esp8266_16mb", "esp32_4mb3f", "esp32c3m_4mb", "esp32s2_4mb", "esp32s3_16mb"]' + +name: Build Firmware + +on: + workflow_dispatch: + +jobs: + generate-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set_matrix.outputs.json }} + steps: + - name: Prepare matrix JSON Object + id: set_matrix + run: echo "json={\"board\":${{ env.BOARDS }}}" >> $GITHUB_OUTPUT + + build: + needs: [ generate-matrix ] + runs-on: ubuntu-latest + strategy: + matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} + + steps: + - uses: actions/checkout@v2 + with: + ref: 'ver4dev' + - name: Run PrepareProject.py -b ${{ matrix.board }} + run: python3 ./PrepareProject.py -b ${{ matrix.board }} + - name: Set up Python + uses: actions/setup-python@v4 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install platformio + - name: Run PlatformIO + if: always() + run: platformio run + - name: Build FS + if: always() + run: platformio run -t buildfs --disable-auto-clean + - name: Rearrange Artifacts + run: | + mkdir -p artifacts/${{ matrix.board }} + find .pio/build/${{ matrix.board }} -name "*.bin" -exec mv {} artifacts/${{ matrix.board }} \; + working-directory: ${{ github.workspace }} + - name: Attach artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }}-${{ github.run_number }} + path: artifacts/${{ matrix.board }} \ No newline at end of file diff --git a/PrepareProject.py b/PrepareProject.py index d746f2f6..11eda3f5 100644 --- a/PrepareProject.py +++ b/PrepareProject.py @@ -13,6 +13,8 @@ # python PrepareProject.py --profile <ИмяФайла> # python PrepareProject.py -p <ИмяФайла> # +# Используя параметры -b или --board можно уточнить для какой платы нужно подготовить проект +# # поддерживаемые контроллеры (профили): # esp8266_4mb # esp8266_16mb @@ -41,7 +43,22 @@ def printHelp(): PrepareProject.py -p --profile -u --update - -h --help''') + -h --help + -b --board ''') + with open('myProfile.json', "r", encoding='utf-8') as read_file: + profJson = json.load(read_file) + print ('') + print ('Choose a board from the list:') + # print(profJson['projectProp']['platformio']['comments_default_envs']) + print (' ', end='') + cnt = 0 + for envs in profJson['projectProp']['platformio']['envs']: + if cnt == 5: + cnt = 0 + print('') + print(' ', end='') + print(envs['name'] + ', ', end='') + cnt = cnt + 1 def updateModulesInProfile(profJson): @@ -68,10 +85,11 @@ def updateModulesInProfile(profJson): update = False # признак необходимости обновить список модулей profile = 'myProfile.json' # имя профиля. Будет заменено из консоли, если указано при старте - +selectDevice = '' # имя платы для которой хотим собрать, если её указали к командной строке -b + argv = sys.argv[1:] try: - opts, args = getopt.getopt(argv, 'hp:u', ['help', 'profile=', 'update']) + opts, args = getopt.getopt(argv, 'hp:ub:', ['help', 'profile=', 'update', 'board=']) except getopt.GetoptError: print('Ошибка обработки параметров!') printHelp() @@ -82,10 +100,14 @@ def updateModulesInProfile(profJson): printHelp() sys.exit() elif opt in ("-p", "--profile"): + print('Загрузка профиля из файла:' + arg) profile = arg elif opt in ("-u", "--update"): + print('Создание новой конфигурации по исходным файлам!') update = True - + elif opt in ("-b", "--board"): + print('Создание профиля для платы:' + arg) + selectDevice = arg if Path(profile).is_file(): # подтягиваем уже существующий профиль @@ -121,9 +143,17 @@ def updateModulesInProfile(profJson): with open(profile, "w", encoding='utf-8') as write_file: json.dump(profJson, write_file, ensure_ascii=False, indent=4, sort_keys=False) - -# определяем какое устройство используется в профиле -deviceName = profJson['projectProp']['platformio']['default_envs'] +deviceName = '' +if selectDevice == '': + # определяем какое устройство используется в профиле + deviceName = profJson['projectProp']['platformio']['default_envs'] +else: + for envs in profJson['projectProp']['platformio']['envs']: + if envs['name'] == selectDevice: + deviceName = selectDevice + if deviceName == '': + deviceName = profJson['projectProp']['platformio']['default_envs'] + print(f"\x1b[1;31;31m Board ", selectDevice, " not found in ",profile,"!!! Use ",deviceName," \x1b[0m") # заполняем папку /data файлами прошивки в зависимости от устройства if deviceName == 'esp8266_1mb_ota' or deviceName == 'esp8285_1mb_ota' or deviceName == 'esp8266_2mb_ota': @@ -134,7 +164,8 @@ def updateModulesInProfile(profJson): deviceType = 'esp32*' if not 'esp32' in deviceName: deviceType = 'esp82*' - +if 'bk72' in deviceName: + deviceType = 'bk72*' # генерируем файлы проекта на основе подготовленного профиля # заполняем конфигурационный файл прошивки параметрами из профиля with open("data_svelte/settings.json", "r", encoding='utf-8') as read_file: @@ -151,7 +182,8 @@ def updateModulesInProfile(profJson): # параллельно собираем необходимые активным модулям библиотеки для включения в компиляцию для текущего типа устройства (esp8266_4m, esp32_4mb, esp8266_1m, esp8266_1m_ota) activeModulesName = [] # список имен активных модулей allLibs = "" # подборка всех библиотек необходимых модулям для дальнейшей записи в конфигурацию platformio -itemsCount = 1; +allDefs = "\n" # для каждого модуля создаем глобальный define +itemsCount = 1 includeDirs = "" # подборка путей ко всем модулям для дальнейшей записи в конфигурацию platformio itemsJson = json.loads('[{"name": "Выберите элемент", "num": 0}]') for section, modules in profJson['modules'].items(): @@ -160,6 +192,8 @@ def updateModulesInProfile(profJson): if module['active']: with open(module['path'] + "/modinfo.json", "r", encoding='utf-8') as read_file: moduleJson = json.load(read_file) + if 'moduleDefines' in moduleJson['about']: + allDefs = allDefs + "\n".join("-D" + d for d in moduleJson['about']['moduleDefines']) if deviceName in moduleJson['usedLibs']: # проверяем поддерживает ли модуль текущее устройство if not 'exclude' in moduleJson['usedLibs'][deviceName]: # смотрим не нужно ли исключить данный модуль из указанной платы deviceName activeModulesName.append(moduleJson['about']['moduleName']) # запоминаем имена для использования на след шагах @@ -170,6 +204,7 @@ def updateModulesInProfile(profJson): configItemsJson['num'] = itemsCount configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] itemsCount = itemsCount + 1 + configItemsJson['moduleName'] = moduleJson['about']['moduleName'] itemsJson.append(configItemsJson) else: # В первую очередь ищем по имени deviceName, чтобы для данной платы можно было уточнить либы. Если не нашли плату по имени в usedLibs пробуем найти её по типу deviceType if deviceType in moduleJson['usedLibs']: # проверяем поддерживает ли модуль текущее устройство @@ -179,9 +214,10 @@ def updateModulesInProfile(profJson): allLibs = allLibs + "\n" + libPath for configItemsJson in moduleJson['configItem']: configItemsJson['num'] = itemsCount - configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] - itemsCount = itemsCount + 1 - itemsJson.append(configItemsJson) + configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] + itemsCount = itemsCount + 1 + itemsJson.append(configItemsJson) + configItemsJson['moduleName'] = moduleJson['about']['moduleName'] with open("data_svelte/items.json", "w", encoding='utf-8') as write_file: json.dump(itemsJson, write_file, ensure_ascii=False, indent=4, sort_keys=False) @@ -192,12 +228,12 @@ def updateModulesInProfile(profJson): allAPI_exec = "" for activModuleName in activeModulesName: allAPI_head = allAPI_head + "\nvoid* getAPI_" + activModuleName + "(String subtype, String params);" - allAPI_exec = allAPI_exec + "\nif ((tmpAPI = getAPI_" + activModuleName + "(subtype, params)) != nullptr) return tmpAPI;" + allAPI_exec = allAPI_exec + "\nif ((tmpAPI = getAPI_" + activModuleName + "(subtype, params)) != nullptr) foundAPI = tmpAPI;" apicpp = '#include "ESPConfiguration.h"\n' apicpp = apicpp + allAPI_head -apicpp = apicpp + '\n\nvoid* getAPI(String subtype, String params) {\nvoid* tmpAPI;' +apicpp = apicpp + '\n\nvoid* getAPI(String subtype, String params) {\nvoid* tmpAPI; void* foundAPI = nullptr;' apicpp = apicpp + allAPI_exec -apicpp = apicpp + '\nreturn nullptr;\n}' +apicpp = apicpp + '\nreturn foundAPI;\n}' with open('src/modules/API.cpp', 'w') as f: f.write(apicpp) @@ -217,7 +253,10 @@ def updateModulesInProfile(profJson): config.read("platformio.ini") config["env:" + deviceName + "_fromitems"]["lib_deps"] = allLibs config["env:" + deviceName + "_fromitems"]["build_src_filter"] = includeDirs +config["env:" + deviceName + "_fromitems"]["build_flags"] = allDefs config["platformio"]["default_envs"] = deviceName +if "${env:" + deviceName + "_fromitems.build_flags}" not in config["env:" + deviceName]["build_flags"]: + config["env:" + deviceName]["build_flags"] += "\n${env:" + deviceName + "_fromitems.build_flags}" # config["platformio"]["data_dir"] = profJson['projectProp']['platformio']['data_dir'] with open("platformio.ini", 'w') as configFile: config.write(configFile) @@ -251,4 +290,4 @@ def updateModulesInProfile(profJson): # print(f"\x1b[1;32;41m Операция завершена. \x1b[0m") - \ No newline at end of file + diff --git a/PrepareServer.py b/PrepareServer.py index 7289a2f5..d0750773 100644 --- a/PrepareServer.py +++ b/PrepareServer.py @@ -26,8 +26,8 @@ def copyFileIfExist(fileName, deviceName): deviceName = config["platformio"]["default_envs"] homeDir = os.path.expanduser('~') -os.system(homeDir + "\.platformio\penv\Scripts\pio run") -os.system(homeDir + "\.platformio\penv\Scripts\pio run -t buildfs --disable-auto-clean") +os.system(homeDir + "/.platformio/penv/Scripts/pio run") +os.system(homeDir + "/.platformio/penv/Scripts/pio run -t buildfs --disable-auto-clean") if copyFileIfExist("firmware.bin", deviceName) and copyFileIfExist("littlefs.bin", deviceName): copyFileIfExist("partitions.bin", deviceName) diff --git a/build_esp32c3m.log b/build_esp32c3m.log new file mode 100644 index 00000000..2035da4b --- /dev/null +++ b/build_esp32c3m.log @@ -0,0 +1,59 @@ +Processing esp32c3m_4mb (framework: arduino; board: lolin_c3_mini; platform: espressif32 @6.6.0) +-------------------------------------------------------------------------------- +Verbose mode can be enabled via `-v, --verbose` option +PLATFORMIO_DIRC:\Users\IT-TehNika\.platformio + , ! C:\Users\IT-TehNika\.platformio\packages\framework-arduinoespressif32\libraries\WiFi\src\WiFiClient.cpp +CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/lolin_c3_mini.html +PLATFORM: Espressif 32 (6.6.0) > WEMOS LOLIN C3 Mini +HARDWARE: ESP32C3 160MHz, 320KB RAM, 4MB Flash +DEBUG: Current (esp-prog) External (cmsis-dap, esp-bridge, esp-builtin, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa) +PACKAGES: + - framework-arduinoespressif32 @ 3.20014.231204 (2.0.14) + - tool-esptoolpy @ 1.40501.0 (4.5.1) + - toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 +LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf +LDF Modes: Finder ~ chain, Compatibility ~ soft +Found 67 compatible libraries +Scanning dependencies... +Dependency Graph +|-- ArduinoJson @ 6.18.0 +|-- PubSubClient @ 2.8.0 +|-- EncButton @ 2.0.0 +|-- TM16xx LEDs and Buttons @ 0.7.2503+sha.ab716f4 +|-- Adafruit GFX Library @ 1.12.4+sha.e4df4cc +|-- Adafruit BusIO @ 1.17.4 +|-- Adafruit BME280 Library @ 2.3.0 +|-- Adafruit BMP280 Library @ 2.6.8 +|-- DHT sensor library for ESPx @ 1.19.0 +|-- MAX6675 library @ 1.1.2 +|-- WS2812FX @ 1.4.5 +|-- dscKeybusInterface @ 3.0.0+sha.ce12e1c +|-- AHTxx @ 1.2.0+sha.701814c +|-- DallasTemperature @ 4.0.5 +|-- EspSoftwareSerial @ 8.1.0 +|-- Wire @ 2.0.0 +|-- OneWire @ 2.3.8 +|-- iarduino RTC ( ) @ 2.0.0 +|-- ESP32 Async UDP @ 2.0.0 +|-- LittleFS @ 2.0.0 +|-- FS @ 2.0.0 +|-- SPIFFS @ 2.0.0 +|-- WiFi @ 2.0.0 +|-- HTTPClient @ 2.0.0 +|-- HTTPUpdate @ 2.0.0 +|-- TickerScheduler +|-- Update @ 2.0.0 +|-- WebServer @ 2.0.0 +|-- WebSockets @ 2.3.7 +Building in release mode +Retrieving maximum program size .pio\build\esp32c3m_4mb\firmware.elf +Checking size .pio\build\esp32c3m_4mb\firmware.elf +Advanced Memory Usage is available via "PlatformIO Home > Project Inspect" +RAM: [= ] 13.1% (used 42884 bytes from 327680 bytes) +Flash: [======== ] 77.7% (used 1221944 bytes from 1572864 bytes) +========================= [SUCCESS] Took 68.72 seconds ========================= + +Environment Status Duration +------------- -------- ------------ +esp32c3m_4mb SUCCESS 00:01:08.724 +========================= 1 succeeded in 00:01:08.724 ========================= diff --git a/build_esp32c3m_4mb.log b/build_esp32c3m_4mb.log new file mode 100644 index 00000000..9cb04a1e Binary files /dev/null and b/build_esp32c3m_4mb.log differ diff --git a/compilerProfile.json b/compilerProfile.json index bac5d993..59ad8d35 100644 --- a/compilerProfile.json +++ b/compilerProfile.json @@ -3,29 +3,37 @@ "name": "IoTmanagerVer4", "apssid": "IoTmanager", "appass": "", - "routerssid": "iot", - "routerpass": "hostel3333", + "wifirep_apchanel": 7, + "wifirep_apip": "192.168.4.1", + "wifirep_staip": "192.168.1.160", + "wifirep_netmask": "255.255.255.0", + "wifirep_gateway": "192.168.4.1", + "wifirep_dns": "192.168.4.1", + "routerssid": "HomeNET", + "routerpass": "Wi73Jktu0205", "timezone": 2, "ntp": "pool.ntp.org", "weblogin": "admin", "webpass": "admin", - "mqttServer": "", - "mqttPort": 8021, - "mqttPrefix": "/risenew", - "mqttUser": "rise", - "mqttPass": "3hostel3", + "mqttServer": "192.168.88.10", + "mqttPort": 1883, + "mqttPrefix": "/IoTmanager", + "mqttUser": "", + "mqttPass": "", "serverip": "http://iotmanager.org", + "serverlocal": "http://192.168.1.2:5500", "log": 0, "mqttin": 0, - "pinSCL": 0, - "pinSDA": 0, + "pinSCL": 9, + "pinSDA": 8, "i2cFreq": 100000, - "wg": "group1" + "wg": "group1", + "debugTraceMsgTlgrm": 1 }, "projectProp": { "platformio": { - "default_envs": "esp8266_4mb", - "comments_default_envs": "choose from: esp8266_4mb or esp32_4mb or esp32cam_4mb or esp32s2_4mb or esp32_4mb3f or esp32s3_16mb or esp32c3m_4mb or esp8266_1mb or esp8266_1mb_ota or esp8285_1mb or esp8285_1mb_ota", + "default_envs": "esp32_4mb3f", + "comments_default_envs": "choose from: esp8266_4mb, esp32_4mb, esp32_4mb3f, esp8266_16mb, esp32_16mb, esp32cam_4mb, esp32s2_4mb, esp32s3_16mb, esp32c3m_4mb, esp8266_1mb, esp8266_1mb_ota, esp8266_2mb, esp8266_2mb_ota, esp8285_1mb, esp8285_1mb_ota, esp32c6_4mb, esp32c6_8mb, bk7231n, esp32_wifirep", "envs": [ { "name": "esp8266_4mb", @@ -53,6 +61,14 @@ "partitions": "0x8000", "littlefs": "0x310000" }, + { + "name": "esp32_wifirep", + "boot_app0": "0xe000", + "bootloader_qio_80m": "0x1000", + "firmware": "0x10000", + "partitions": "0x8000", + "littlefs": "0x310000" + }, { "name": "esp32cam_4mb", "boot_app0": "0xe000", @@ -122,6 +138,22 @@ "firmware": "0x10000", "partitions": "0x8000", "littlefs": "0x910000" + }, + { + "name": "esp32c6_4mb", + "boot_app0": "0xe000", + "bootloader_qio_80m": "0x1000", + "firmware": "0x10000", + "partitions": "0x8000", + "littlefs": "0x310000" + }, + { + "name": "esp32c6_8mb", + "boot_app0": "0xe000", + "bootloader_qio_80m": "0x1000", + "firmware": "0x10000", + "partitions": "0x8000", + "littlefs": "0x670000" } ] } @@ -136,6 +168,14 @@ "path": "src/modules/virtual/Cron", "active": true }, + { + "path": "src/modules/virtual/DiscoveryHA", + "active": false + }, + { + "path": "src/modules/virtual/DiscoveryHomeD", + "active": false + }, { "path": "src/modules/virtual/GoogleSheet", "active": false @@ -144,10 +184,22 @@ "path": "src/modules/virtual/Loging", "active": true }, + { + "path": "src/modules/virtual/Loging2", + "active": false + }, + { + "path": "src/modules/virtual/Loging3", + "active": false + }, { "path": "src/modules/virtual/LogingDaily", "active": true }, + { + "path": "src/modules/virtual/LogingHourly", + "active": true + }, { "path": "src/modules/virtual/Math", "active": true @@ -164,6 +216,10 @@ "path": "src/modules/virtual/Timer", "active": true }, + { + "path": "src/modules/virtual/UpdateServer", + "active": true + }, { "path": "src/modules/virtual/Variable", "active": true @@ -178,17 +234,13 @@ } ], "sensors": [ - { - "path": "src/modules/exec/Pcf8591", - "active": false - }, { "path": "src/modules/sensors/A02Distance", - "active": true + "active": false }, { "path": "src/modules/sensors/Acs712", - "active": true + "active": false }, { "path": "src/modules/sensors/Ads1115", @@ -208,7 +260,11 @@ }, { "path": "src/modules/sensors/BL0937", - "active": true + "active": false + }, + { + "path": "src/modules/sensors/BL0942", + "active": false }, { "path": "src/modules/sensors/Ble", @@ -242,6 +298,10 @@ "path": "src/modules/sensors/DS2401", "active": false }, + { + "path": "src/modules/sensors/DscKeybus", + "active": false + }, { "path": "src/modules/sensors/Emon", "active": false @@ -306,12 +366,24 @@ "path": "src/modules/sensors/Mhz19", "active": false }, + { + "path": "src/modules/sensors/ModbusRTU", + "active": false + }, + { + "path": "src/modules/sensors/ModbusRTUasync", + "active": false + }, { "path": "src/modules/sensors/MQgas", - "active": true + "active": false }, { "path": "src/modules/sensors/Ntc", + "active": true + }, + { + "path": "src/modules/sensors/Pcf8591", "active": false }, { @@ -320,7 +392,7 @@ }, { "path": "src/modules/sensors/Pzem004t_v2", - "active": true + "active": false }, { "path": "src/modules/sensors/RCswitch", @@ -332,7 +404,7 @@ }, { "path": "src/modules/sensors/S8", - "active": true + "active": false }, { "path": "src/modules/sensors/Scd40", @@ -348,15 +420,15 @@ }, { "path": "src/modules/sensors/Sht20", - "active": true + "active": false }, { "path": "src/modules/sensors/Sht30", - "active": true + "active": false }, { "path": "src/modules/sensors/Sonar", - "active": true + "active": false }, { "path": "src/modules/sensors/UART", @@ -382,7 +454,11 @@ }, { "path": "src/modules/exec/Buzzer", - "active": true + "active": false + }, + { + "path": "src/modules/exec/EctoControlAdapter", + "active": false }, { "path": "src/modules/exec/Enconder", @@ -402,7 +478,11 @@ }, { "path": "src/modules/exec/IoTServo", - "active": true + "active": false + }, + { + "path": "src/modules/exec/IRremote", + "active": false }, { "path": "src/modules/exec/Mcp23008", @@ -410,11 +490,15 @@ }, { "path": "src/modules/exec/Mcp23017", - "active": true + "active": false + }, + { + "path": "src/modules/exec/MilightHub", + "active": false }, { "path": "src/modules/exec/Mp3", - "active": true + "active": false }, { "path": "src/modules/exec/Multitouch", @@ -426,7 +510,7 @@ }, { "path": "src/modules/exec/Pcf8574", - "active": true + "active": false }, { "path": "src/modules/exec/Pwm32", @@ -434,7 +518,7 @@ }, { "path": "src/modules/exec/Pwm8266", - "active": true + "active": false }, { "path": "src/modules/exec/SDcard", @@ -452,6 +536,10 @@ "path": "src/modules/exec/SysExt", "active": false }, + { + "path": "src/modules/exec/Tca9555", + "active": false + }, { "path": "src/modules/exec/Telegram", "active": false @@ -466,7 +554,7 @@ }, { "path": "src/modules/exec/Thermostat", - "active": false + "active": true }, { "path": "src/modules/sensors/Ds2423", @@ -476,11 +564,19 @@ "screens": [ { "path": "src/modules/display/DwinI", - "active": true + "active": false + }, + { + "path": "src/modules/display/GyverLAMP", + "active": false }, { "path": "src/modules/display/Lcd2004", - "active": true + "active": false + }, + { + "path": "src/modules/display/LedFX", + "active": false }, { "path": "src/modules/display/Nextion", @@ -496,16 +592,20 @@ }, { "path": "src/modules/display/Oled64", - "active": true + "active": false }, { "path": "src/modules/display/Smi2_m", - "active": true + "active": false }, { "path": "src/modules/display/TM16XX", "active": false }, + { + "path": "src/modules/display/U8g2lib", + "active": false + }, { "path": "src/modules/display/Ws2812b", "active": false diff --git a/data_full/build/bundle.css.gz b/data_full/build/bundle.css.gz index 29ab1fa8..19d2f663 100644 Binary files a/data_full/build/bundle.css.gz and b/data_full/build/bundle.css.gz differ diff --git a/data_full/build/bundle.js.gz b/data_full/build/bundle.js.gz index 24f6ab8b..d4dc70e4 100644 Binary files a/data_full/build/bundle.js.gz and b/data_full/build/bundle.js.gz differ diff --git a/data_full/index.html b/data_full/index.html index bbcd166f..fe91a206 100644 --- a/data_full/index.html +++ b/data_full/index.html @@ -4,7 +4,7 @@ - IoT Manager 4.5.5 + IoT Manager 4.6.2 diff --git a/data_svelte/build/bundle.css.gz b/data_svelte/build/bundle.css.gz index 29ab1fa8..19d2f663 100644 Binary files a/data_svelte/build/bundle.css.gz and b/data_svelte/build/bundle.css.gz differ diff --git a/data_svelte/build/bundle.js.gz b/data_svelte/build/bundle.js.gz index 24f6ab8b..d4dc70e4 100644 Binary files a/data_svelte/build/bundle.js.gz and b/data_svelte/build/bundle.js.gz differ diff --git a/data_svelte/flashProfile.json b/data_svelte/flashProfile.json index 337a4283..af00edb5 100644 --- a/data_svelte/flashProfile.json +++ b/data_svelte/flashProfile.json @@ -1,7 +1,7 @@ { "projectProp": { "platformio": { - "default_envs": "esp8266_4mb" + "default_envs": "esp32_4mb3f" } }, "modules": { @@ -14,6 +14,14 @@ "path": "src/modules/virtual/Cron", "active": true }, + { + "path": "src/modules/virtual/DiscoveryHA", + "active": false + }, + { + "path": "src/modules/virtual/DiscoveryHomeD", + "active": false + }, { "path": "src/modules/virtual/GoogleSheet", "active": false @@ -22,10 +30,22 @@ "path": "src/modules/virtual/Loging", "active": true }, + { + "path": "src/modules/virtual/Loging2", + "active": false + }, + { + "path": "src/modules/virtual/Loging3", + "active": false + }, { "path": "src/modules/virtual/LogingDaily", "active": true }, + { + "path": "src/modules/virtual/LogingHourly", + "active": true + }, { "path": "src/modules/virtual/Math", "active": true @@ -42,6 +62,10 @@ "path": "src/modules/virtual/Timer", "active": true }, + { + "path": "src/modules/virtual/UpdateServer", + "active": true + }, { "path": "src/modules/virtual/Variable", "active": true @@ -56,17 +80,13 @@ } ], "sensors": [ - { - "path": "src/modules/exec/Pcf8591", - "active": false - }, { "path": "src/modules/sensors/A02Distance", - "active": true + "active": false }, { "path": "src/modules/sensors/Acs712", - "active": true + "active": false }, { "path": "src/modules/sensors/Ads1115", @@ -86,7 +106,11 @@ }, { "path": "src/modules/sensors/BL0937", - "active": true + "active": false + }, + { + "path": "src/modules/sensors/BL0942", + "active": false }, { "path": "src/modules/sensors/Ble", @@ -120,6 +144,10 @@ "path": "src/modules/sensors/DS2401", "active": false }, + { + "path": "src/modules/sensors/DscKeybus", + "active": false + }, { "path": "src/modules/sensors/Emon", "active": false @@ -184,12 +212,24 @@ "path": "src/modules/sensors/Mhz19", "active": false }, + { + "path": "src/modules/sensors/ModbusRTU", + "active": false + }, + { + "path": "src/modules/sensors/ModbusRTUasync", + "active": false + }, { "path": "src/modules/sensors/MQgas", - "active": true + "active": false }, { "path": "src/modules/sensors/Ntc", + "active": true + }, + { + "path": "src/modules/sensors/Pcf8591", "active": false }, { @@ -198,7 +238,7 @@ }, { "path": "src/modules/sensors/Pzem004t_v2", - "active": true + "active": false }, { "path": "src/modules/sensors/RCswitch", @@ -210,7 +250,7 @@ }, { "path": "src/modules/sensors/S8", - "active": true + "active": false }, { "path": "src/modules/sensors/Scd40", @@ -226,15 +266,15 @@ }, { "path": "src/modules/sensors/Sht20", - "active": true + "active": false }, { "path": "src/modules/sensors/Sht30", - "active": true + "active": false }, { "path": "src/modules/sensors/Sonar", - "active": true + "active": false }, { "path": "src/modules/sensors/UART", @@ -260,7 +300,11 @@ }, { "path": "src/modules/exec/Buzzer", - "active": true + "active": false + }, + { + "path": "src/modules/exec/EctoControlAdapter", + "active": false }, { "path": "src/modules/exec/Enconder", @@ -280,7 +324,11 @@ }, { "path": "src/modules/exec/IoTServo", - "active": true + "active": false + }, + { + "path": "src/modules/exec/IRremote", + "active": false }, { "path": "src/modules/exec/Mcp23008", @@ -288,11 +336,15 @@ }, { "path": "src/modules/exec/Mcp23017", - "active": true + "active": false + }, + { + "path": "src/modules/exec/MilightHub", + "active": false }, { "path": "src/modules/exec/Mp3", - "active": true + "active": false }, { "path": "src/modules/exec/Multitouch", @@ -304,7 +356,7 @@ }, { "path": "src/modules/exec/Pcf8574", - "active": true + "active": false }, { "path": "src/modules/exec/Pwm32", @@ -312,7 +364,7 @@ }, { "path": "src/modules/exec/Pwm8266", - "active": true + "active": false }, { "path": "src/modules/exec/SDcard", @@ -330,6 +382,10 @@ "path": "src/modules/exec/SysExt", "active": false }, + { + "path": "src/modules/exec/Tca9555", + "active": false + }, { "path": "src/modules/exec/Telegram", "active": false @@ -344,7 +400,7 @@ }, { "path": "src/modules/exec/Thermostat", - "active": false + "active": true }, { "path": "src/modules/sensors/Ds2423", @@ -354,11 +410,19 @@ "screens": [ { "path": "src/modules/display/DwinI", - "active": true + "active": false + }, + { + "path": "src/modules/display/GyverLAMP", + "active": false }, { "path": "src/modules/display/Lcd2004", - "active": true + "active": false + }, + { + "path": "src/modules/display/LedFX", + "active": false }, { "path": "src/modules/display/Nextion", @@ -374,16 +438,20 @@ }, { "path": "src/modules/display/Oled64", - "active": true + "active": false }, { "path": "src/modules/display/Smi2_m", - "active": true + "active": false }, { "path": "src/modules/display/TM16XX", "active": false }, + { + "path": "src/modules/display/U8g2lib", + "active": false + }, { "path": "src/modules/display/Ws2812b", "active": false diff --git a/data_svelte/index.html b/data_svelte/index.html index bbcd166f..fe91a206 100644 --- a/data_svelte/index.html +++ b/data_svelte/index.html @@ -4,7 +4,7 @@ - IoT Manager 4.5.5 + IoT Manager 4.6.2 diff --git a/data_svelte/items.json b/data_svelte/items.json index 8c476da9..5ea4dedb 100644 --- a/data_svelte/items.json +++ b/data_svelte/items.json @@ -19,7 +19,8 @@ "val": "*/15 * * * * *", "formatNextAlarm": "%H:%M:%S", "needSave": 0, - "num": 1 + "num": 1, + "moduleName": "Cron" }, { "global": 0, @@ -33,7 +34,10 @@ "num": 2, "int": 5, "logid": "t", - "points": 300 + "daysSave": 5, + "daysShow": 0, + "points": 300, + "moduleName": "Loging" }, { "global": 0, @@ -46,7 +50,10 @@ "descr": "Температура", "int": 0, "num": 3, - "points": 300 + "daysSave": 5, + "daysShow": 0, + "points": 300, + "moduleName": "Loging" }, { "global": 0, @@ -60,26 +67,47 @@ "num": 4, "int": 1, "logid": "t", - "points": 200, + "points": 365, "telegram": 0, "test": 0, "btn-defvalue": 0, - "btn-reset": "nil" + "btn-reset": "nil", + "moduleName": "LogingDaily" }, { "global": 0, - "name": "5. Math library", + "name": "5. График часового расхода", + "type": "Writing", + "subtype": "LogingHourly", + "id": "logh", + "widget": "chart3", + "page": "Графики", + "descr": "Расход в час", + "num": 5, + "int": 1, + "logid": "", + "points": 24, + "telegram": 0, + "test": 0, + "btn-defvalue": 0, + "btn-reset": "nil", + "moduleName": "LogingHourly" + }, + { + "global": 0, + "name": "6. Math library", "type": "Reading", "subtype": "IoTMath", "id": "math", "widget": "anydataValue", "page": "Математика", "descr": "", - "num": 5 + "num": 6, + "moduleName": "IoTMath" }, { "global": 0, - "name": "6. Погода OWM", + "name": "7. Погода OWM", "type": "Reading", "subtype": "owmWeather", "id": "owm", @@ -97,11 +125,12 @@ "round": 1, "val": "...", "debug": 0, - "num": 6 + "num": 7, + "moduleName": "owmWeather" }, { "global": 0, - "name": "7. Ping", + "name": "8. Ping", "type": "Reading", "subtype": "Ping", "id": "ping", @@ -115,11 +144,12 @@ "data_size": 0, "count": 0, "tos": 0, - "num": 7 + "num": 8, + "moduleName": "Ping" }, { "global": 0, - "name": "8. Таймер", + "name": "9. Таймер", "type": "Writing", "subtype": "Timer", "id": "timer", @@ -131,11 +161,27 @@ "ticker": 1, "repeat": 1, "needSave": 0, - "num": 8 + "num": 9, + "moduleName": "Timer" + }, + { + "global": 0, + "name": "10. Свой сервер обновлений", + "type": "Reading", + "subtype": "UpdateServer", + "id": "UpdateServer", + "widget": "", + "page": "", + "descr": "", + "btn-startUpdateAll": "http://192.168.11.112/iotm/esp8266_4mb/400", + "btn-startUpdateFS": "http://192.168.11.112/iotm/esp8266_4mb/400", + "btn-startUpdateFW": "http://192.168.11.112/iotm/esp8266_4mb/400", + "num": 10, + "moduleName": "UpdateServer" }, { "global": 0, - "name": "9. Окно ввода числа (переменная)", + "name": "11. Окно ввода числа (переменная)", "type": "Reading", "subtype": "Variable", "id": "value", @@ -149,11 +195,12 @@ "plus": 0, "multiply": 1, "round": 0, - "num": 9 + "num": 11, + "moduleName": "Variable" }, { "global": 0, - "name": "10. Окно ввода времени", + "name": "12. Окно ввода времени", "type": "Reading", "subtype": "Variable", "id": "time", @@ -163,11 +210,12 @@ "descr": "Введите время", "int": "0", "val": "02:00", - "num": 10 + "num": 12, + "moduleName": "Variable" }, { "global": 0, - "name": "11. Окно ввода даты", + "name": "13. Окно ввода даты", "type": "Reading", "subtype": "Variable", "id": "time", @@ -177,11 +225,12 @@ "descr": "Введите дату", "int": "0", "val": "24.05.2022", - "num": 11 + "num": 13, + "moduleName": "Variable" }, { "global": 0, - "name": "12. Окно ввода текста", + "name": "14. Окно ввода текста", "type": "Reading", "subtype": "Variable", "id": "txt", @@ -191,11 +240,12 @@ "descr": "Введите текст", "int": "0", "val": "текст", - "num": 12 + "num": 14, + "moduleName": "Variable" }, { "global": 0, - "name": "13. Вывод значения", + "name": "15. Вывод значения", "type": "Reading", "subtype": "Variable", "id": "vout", @@ -209,11 +259,12 @@ "plus": 0, "multiply": 1, "round": 0, - "num": 13 + "num": 15, + "moduleName": "Variable" }, { "global": 0, - "name": "14. Виртуальная кнопка", + "name": "16. Виртуальная кнопка", "type": "Reading", "subtype": "VButton", "id": "vbtn", @@ -223,45 +274,12 @@ "descr": "Кнопка", "int": "0", "val": "0", - "num": 14 + "num": 16, + "moduleName": "VButton" }, { "header": "sensors" }, - { - "name": "15. A02 Дальность", - "type": "Reading", - "subtype": "A02Distance", - "id": "dist", - "widget": "anydataCm", - "page": "Сенсоры", - "descr": "Дальность", - "int": 5, - "round": 1, - "tx": 17, - "rx": 16, - "line": 2, - "speed": 9600, - "num": 15 - }, - { - "name": "16. Acs712 Ток", - "type": "Reading", - "subtype": "Acs712", - "id": "amp", - "widget": "anydataAmp", - "page": "Сенсоры", - "descr": "Ток", - "round": 3, - "pin": 39, - "int": 5, - "rms": 1, - "vref": 5000, - "sens": 100, - "adczero": 512, - "btn-setZero": "nil", - "num": 16 - }, { "global": 0, "name": "17. AHTXX Температура", @@ -275,7 +293,8 @@ "addr": "0x38", "shtType": 1, "round": 1, - "num": 17 + "num": 17, + "moduleName": "AhtXX" }, { "global": 0, @@ -290,7 +309,8 @@ "addr": "0x38", "shtType": 1, "round": 1, - "num": 18 + "num": 18, + "moduleName": "AhtXX" }, { "global": 0, @@ -308,111 +328,12 @@ "pin": 0, "int": 15, "avgSteps": 1, - "num": 19 + "num": 19, + "moduleName": "AnalogAdc" }, { "global": 0, - "name": "20. BL0937 Напряжение", - "type": "Reading", - "subtype": "BL0937v", - "id": "bl_v", - "widget": "anydataVlt", - "page": "BL0937", - "descr": "Напряжение", - "int": 15, - "round": 1, - "num": 20 - }, - { - "global": 0, - "name": "21. BL0937 Сила тока", - "type": "Reading", - "subtype": "BL0937a", - "id": "bl_a", - "widget": "anydataAmp", - "page": "BL0937", - "descr": "Сила тока", - "int": 15, - "round": 1, - "num": 21 - }, - { - "global": 0, - "name": "22. BL0937 Мощность", - "type": "Reading", - "subtype": "BL0937w", - "id": "bl_w", - "widget": "anydataWt", - "page": "BL0937", - "descr": "Мощность", - "int": 15, - "round": 1, - "num": 22 - }, - { - "global": 0, - "name": "23. BL0937 Реакт.Мощность", - "type": "Reading", - "subtype": "BL0937reactw", - "id": "bl_reactw", - "widget": "anydataWt", - "page": "BL0937", - "descr": "Реакт.Мощность", - "int": 15, - "round": 1, - "num": 23 - }, - { - "global": 0, - "name": "24. BL0937 Активн.Мощность", - "type": "Reading", - "subtype": "BL0937actw", - "id": "bl_actw", - "widget": "anydataWt", - "page": "BL0937", - "descr": "Актив.Мощность", - "int": 15, - "round": 1, - "num": 24 - }, - { - "global": 0, - "name": "25. BL0937 Энергия", - "type": "Reading", - "subtype": "BL0937wh", - "id": "bl_wh", - "widget": "anydataWth", - "page": "BL0937", - "descr": "Энергия", - "int": 15, - "round": 1, - "num": 25 - }, - { - "global": 0, - "name": "26. BL0937 настройка", - "type": "Reading", - "subtype": "BL0937cmd", - "id": "bl_set", - "widget": "nil", - "page": "", - "descr": "", - "btn-reset": "", - "int": "5", - "R_current": 0.001, - "R_upstream": 1000000, - "R_downstream": 1000, - "CF_GPIO": 4, - "CF1_GPIO": 5, - "SEL_GPIO": 12, - "kfV": 0, - "kfA": 0, - "kfW": 0, - "num": 26 - }, - { - "global": 0, - "name": "27. BME280 Температура", + "name": "20. BME280 Температура", "type": "Reading", "subtype": "Bme280t", "id": "Tmp3", @@ -422,11 +343,12 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 27 + "num": 20, + "moduleName": "Bme280" }, { "global": 0, - "name": "28. BME280 Давление", + "name": "21. BME280 Давление", "type": "Reading", "subtype": "Bme280p", "id": "Press3", @@ -436,11 +358,12 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 28 + "num": 21, + "moduleName": "Bme280" }, { "global": 0, - "name": "29. BME280 Влажность", + "name": "22. BME280 Влажность", "type": "Reading", "subtype": "Bme280h", "id": "Hum3", @@ -450,11 +373,12 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 29 + "num": 22, + "moduleName": "Bme280" }, { "global": 0, - "name": "30. BME280 Tочка росы", + "name": "23. BME280 Tочка росы", "type": "Reading", "subtype": "Bme280dp", "id": "Dew3", @@ -464,11 +388,12 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 30 + "num": 23, + "moduleName": "Bme280" }, { "global": 0, - "name": "31. BMP280 Температура", + "name": "24. BMP280 Температура", "type": "Reading", "subtype": "Bmp280t", "id": "tmp3", @@ -478,11 +403,12 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 31 + "num": 24, + "moduleName": "Bmp280" }, { "global": 0, - "name": "32. BMP280 Давление", + "name": "25. BMP280 Давление", "type": "Reading", "subtype": "Bmp280p", "id": "Press3", @@ -492,11 +418,12 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 32 + "num": 25, + "moduleName": "Bmp280" }, { "global": 0, - "name": "33. DHT11 Температура", + "name": "26. DHT11 Температура", "type": "Reading", "subtype": "Dht1122t", "id": "tmp3", @@ -506,11 +433,12 @@ "int": 15, "pin": 0, "senstype": "dht11", - "num": 33 + "num": 26, + "moduleName": "Dht1122" }, { "global": 0, - "name": "34. DHT11 Влажность", + "name": "27. DHT11 Влажность", "type": "Reading", "subtype": "Dht1122h", "id": "Hum3", @@ -520,11 +448,12 @@ "int": 15, "pin": 0, "senstype": "dht11", - "num": 34 + "num": 27, + "moduleName": "Dht1122" }, { "global": 0, - "name": "35. DS18B20 Температура", + "name": "28. DS18B20 Температура", "type": "Reading", "subtype": "Ds18b20", "id": "dstmp", @@ -536,11 +465,12 @@ "index": 0, "addr": "", "round": 1, - "num": 35 + "num": 28, + "moduleName": "Ds18b20" }, { "global": 0, - "name": "36. Аналоговый счетчик импульсов", + "name": "29. Аналоговый счетчик импульсов", "type": "Writing", "subtype": "Impulse", "id": "impulse", @@ -553,166 +483,36 @@ "pinMode": "INPUT", "debounceDelay": 3, "multiply": 1, - "num": 36 + "count": 0, + "timeORcount": 0, + "num": 29, + "moduleName": "Impulse" }, { "global": 0, - "name": "37. MQ газовые анализаторы", + "name": "30. Cенсор температуры NTC", "type": "Reading", - "subtype": "MQgas", - "id": "MQ", - "widget": "anydataPpm", + "subtype": "Ntc", + "id": "Ntctmp", + "widget": "anydataTmp", "page": "Сенсоры", - "descr": "MQ-135", - "Series": 135, - "Gas": "CO2", - "Rl on board": 10, - "Ro in clean air": 0, - "Rl/Ro in clean air": 0, - "PPM in clean air": 397.13, - "aLimit": -0.42, - "bLimit": 1.92, - "Warm up time": 60, - "Sample interval": 20, - "Sample times": 10, - "Calibtation intensity": 5, - "autoCalibration": 1, - "autoCalib.Period": 24, - "TempHum correction": 1, - "temperature": 20, - "idTempSensor": "", - "humidity": 50, - "idHumSensor": "", - "k1": 0.00672096284322792, - "k2": -0.0159038179354688, - "b1": -0.741244323718154, - "b2": 1.77535862501753, - "Debug": 1, - "plus": 0, - "multiply": 1, - "round": 1, - "pin-Esp32": 34, - "operating voltage": 3.3, - "int": 15, - "num": 37 - }, - { - "global": 0, - "name": "38. PZEM 004t Напряжение", - "type": "Reading", - "subtype": "Pzem004v", - "id": "v", - "widget": "anydataVlt", - "page": "PZEM", - "descr": "Напряжение", - "int": 15, - "addr": "0xF8", - "round": 1, - "num": 38 - }, - { - "global": 0, - "name": "39. PZEM 004t Сила тока", - "type": "Reading", - "subtype": "Pzem004a", - "id": "a", - "widget": "anydataAmp", - "page": "PZEM", - "descr": "Сила тока", - "int": 15, - "addr": "0xF8", - "round": 1, - "num": 39 - }, - { - "global": 0, - "name": "40. PZEM 004t Мощность", - "type": "Reading", - "subtype": "Pzem004w", - "id": "w", - "widget": "anydataWt", - "page": "PZEM", - "descr": "Мощность", - "int": 15, - "addr": "0xF8", - "round": 1, - "num": 40 - }, - { - "global": 0, - "name": "41. PZEM 004t Энергия", - "type": "Reading", - "subtype": "Pzem004wh", - "id": "wh", - "widget": "anydataWth", - "page": "PZEM", - "descr": "Энергия", - "int": 15, - "addr": "0xF8", - "round": 1, - "num": 41 - }, - { - "global": 0, - "name": "42. PZEM 004t Частота", - "type": "Reading", - "subtype": "Pzem004hz", - "id": "hz", - "widget": "anydataHtz", - "page": "PZEM", - "descr": "Частота", - "int": 15, - "addr": "0xF8", - "round": 1, - "num": 42 - }, - { - "global": 0, - "name": "43. PZEM 004t Косинус", - "type": "Reading", - "subtype": "Pzem004pf", - "id": "pf", - "widget": "anydata", - "page": "PZEM", - "descr": "Косинус F", + "descr": "NTC Температура", + "needSave": 0, + "val": "0", "int": 15, - "addr": "0xF8", + "pin": "35", + "R1": "10000", + "R0": "10000", + "Beta": "3950.0", + "T0": "25", + "Vs": "3.3", "round": 1, - "num": 43 - }, - { - "global": 0, - "name": "44. PZEM настройка", - "type": "Reading", - "subtype": "Pzem004cmd", - "id": "set", - "widget": "nil", - "page": "", - "descr": "", - "int": 15, - "addr": "0xF8", - "btn-changeaddr": "0x01", - "btn-reset": "", - "num": 44 - }, - { - "global": 0, - "name": "45. PZEM uart", - "type": "Reading", - "subtype": "Pzem004uart", - "id": "upzem", - "widget": "nil", - "page": "", - "descr": "", - "tx": 17, - "rx": 16, - "line": 2, - "speed": 9600, - "num": 45 + "num": 30, + "moduleName": "Ntc" }, { "global": 0, - "name": "46. Часы реального времени", + "name": "31. Часы реального времени", "type": "Reading", "subtype": "RTC", "id": "rtc", @@ -728,91 +528,11 @@ "int": 5, "btn-setUTime": "0", "btn-setSysTime": "nil", - "num": 46 - }, - { - "global": 0, - "name": "47. (S8) Cенсор качества воздуха", - "num": 47, - "type": "Reading", - "subtype": "S8co", - "id": "s8co", - "widget": "anydataPpm", - "page": "Сенсоры", - "descr": "S8_CO2", - "int": 15, - "round": 1, - "rxPin": 13, - "txPin": 15 - }, - { - "global": 0, - "name": "48. Sht20 Температура", - "type": "Reading", - "subtype": "Sht20t", - "id": "tmp2", - "widget": "anydataTmp", - "page": "Сенсоры", - "descr": "Температура", - "int": 15, - "round": 1, - "num": 48 + "num": 31, + "moduleName": "RTC" }, { - "global": 0, - "name": "49. Sht20 Влажность", - "type": "Reading", - "subtype": "Sht20h", - "id": "Hum2", - "widget": "anydataHum", - "page": "Сенсоры", - "descr": "Влажность", - "int": 15, - "round": 1, - "num": 49 - }, - { - "global": 0, - "name": "50. Sht30 Температура", - "type": "Reading", - "subtype": "Sht30t", - "id": "tmp30", - "widget": "anydataTmp", - "page": "Сенсоры", - "descr": "SHT30 Температура", - "int": 15, - "round": 1, - "num": 50 - }, - { - "global": 0, - "name": "51. Sht30 Влажность", - "type": "Reading", - "subtype": "Sht30h", - "id": "Hum30", - "widget": "anydataHum", - "page": "Сенсоры", - "descr": "SHT30 Влажность", - "int": 15, - "round": 1, - "num": 51 - }, - { - "global": 0, - "name": "52. HC-SR04 Ультразвуковой дальномер", - "num": 52, - "type": "Reading", - "subtype": "Sonar", - "id": "sonar", - "widget": "anydataTmp", - "page": "Сенсоры", - "descr": "Расстояние (см)", - "pinTrig": 5, - "pinEcho": 4, - "int": 5 - }, - { - "name": "53. UART", + "name": "32. UART", "type": "Reading", "subtype": "UART", "page": "", @@ -824,14 +544,15 @@ "line": 2, "speed": 9600, "eventFormat": 0, - "num": 53 + "num": 32, + "moduleName": "UART" }, { "header": "executive_devices" }, { "global": 0, - "name": "54. Аналоговая кнопка", + "name": "33. Аналоговая кнопка", "type": "Reading", "subtype": "AnalogBtn", "id": "abtn", @@ -841,11 +562,12 @@ "pin": 34, "aValue": -1, "delta": 50, - "num": 54 + "num": 33, + "moduleName": "AnalogBtn" }, { "global": 0, - "name": "55. Кнопка подключенная к пину", + "name": "34. Кнопка подключенная к пину", "type": "Writing", "subtype": "ButtonIn", "id": "btn", @@ -860,11 +582,12 @@ "debounceDelay": 50, "fixState": 0, "inv": 0, - "num": 55 + "num": 34, + "moduleName": "ButtonIn" }, { "global": 0, - "name": "56. Управление пином", + "name": "35. Управление пином", "type": "Writing", "subtype": "ButtonOut", "needSave": 0, @@ -875,34 +598,12 @@ "int": 0, "inv": 0, "pin": 2, - "num": 56 + "num": 35, + "moduleName": "ButtonOut" }, { "global": 0, - "name": "57. Пассивный звуковой извещатель", - "type": "Writing", - "subtype": "Buzzer", - "id": "buzzer", - "widget": "toggle", - "page": "Кнопки", - "descr": "Buzzer", - "int": 4000, - "pin": 14, - "freq": 2000, - "duration": 1000, - "beatLevel": 4, - "tempo": 120, - "tempoCorrection": 1, - "pauseBetween": 0, - "transpose": 0, - "cycle": 0, - "indication": 1, - "val": 0, - "num": 57 - }, - { - "global": 0, - "name": "58. Энкодер", + "name": "36. Энкодер", "type": "Writing", "subtype": "Encoder", "id": "enc", @@ -915,56 +616,12 @@ "step": 1, "stepOnPress": 5, "pins": "4,5,2", - "num": 58 + "num": 36, + "moduleName": "Encoder" }, { "global": 0, - "name": "59. Сервопривод", - "type": "Writing", - "subtype": "IoTServo", - "id": "servo", - "widget": "rangeServo", - "page": "servo", - "descr": "угол", - "pin": 12, - "minPulseWidth": 544, - "maxPulseWidth": 2400, - "minAngle": 0, - "maxAngle": 180, - "trackingID": "", - "num": 59 - }, - { - "global": 0, - "name": "60. Расширитель портов Mcp23017", - "type": "Reading", - "subtype": "Mcp23017", - "id": "Mcp", - "widget": "", - "page": "", - "descr": "", - "int": "0", - "addr": "0x20", - "index": 1, - "num": 60 - }, - { - "global": 0, - "name": "61. MP3 плеер", - "type": "Reading", - "subtype": "Mp3", - "id": "mp3", - "widget": "", - "page": "", - "descr": "", - "int": 1, - "pins": "14,12", - "volume": 20, - "num": 61 - }, - { - "global": 0, - "name": "62. Сенсорная кнопка", + "name": "37. Сенсорная кнопка", "type": "Writing", "subtype": "Multitouch", "id": "impulse", @@ -978,41 +635,31 @@ "pinMode": "INPUT", "debounceDelay": 50, "PWMDelay": 500, - "num": 62 + "num": 37, + "moduleName": "Multitouch" }, { "global": 0, - "name": "63. Расширитель портов Pcf8574", - "type": "Reading", - "subtype": "Pcf8574", - "id": "Pcf", - "widget": "", - "page": "", - "descr": "", - "int": "0", - "addr": "0x20", - "index": 1, - "num": 63 - }, - { - "global": 0, - "name": "64. PWM ESP8266", + "name": "38. PWM ESP32", "type": "Writing", - "subtype": "Pwm8266", + "subtype": "Pwm32", "id": "pwm", "widget": "range", "page": "Кнопки", "descr": "PWM", "int": 0, - "pin": 15, + "pin": 2, "freq": 5000, + "ledChannel": 2, + "PWM_resolution": 10, "val": 0, "apin": -1, - "num": 64 + "num": 38, + "moduleName": "Pwm32" }, { "global": 0, - "name": "65. Телеграм-Лайт", + "name": "39. Телеграм-Лайт", "type": "Writing", "subtype": "TelegramLT", "id": "tg", @@ -1021,74 +668,92 @@ "descr": "", "token": "", "chatID": "", - "num": 65 + "num": 39, + "moduleName": "TelegramLT" }, { - "header": "screens" + "global": 0, + "needSave": 0, + "name": "40. Термостат Гистере́зис ", + "type": "Writing", + "subtype": "ThermostatGIST", + "id": "Thermo", + "widget": "anydataDef", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "set_id": "", + "term_id": "", + "term_rezerv_id": "", + "gist": 0.1, + "rele": "", + "direction": 0, + "num": 40, + "moduleName": "Thermostat" }, { - "name": "66. LCD Dwin экран", - "type": "Reading", - "subtype": "DwinI", - "id": "dwin", - "widget": "", - "page": "", - "descr": "", - "tx": 17, - "rx": 16, - "line": 2, - "speed": 9600, - "btn-uploadUI": "", - "num": 66 + "global": 0, + "needSave": 0, + "name": "41. Термостат PID", + "type": "Writing", + "subtype": "ThermostatPID", + "id": "Thermo", + "widget": "anydataHum", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "map": "1024,1024,1,100", + "set_id": "", + "term_id": "", + "rele": "", + "setDirection": 0, + "setLimitsMIN": 0, + "setLimitsMAX": 100, + "KP": 10, + "KI": 0.02, + "KD": 8, + "num": 41, + "moduleName": "Thermostat" }, { "global": 0, - "name": "67. LCD экран 2004", - "type": "Reading", - "subtype": "Lcd2004", - "id": "Lcd", - "widget": "inputTxt", - "page": "screens", - "descr": "LCD Экран", - "addr": "0x27", - "size": "20,4", - "coord": "0,0", - "id2show": "", - "prefix": "", - "postfix": "", - "num": 67 + "needSave": 0, + "name": "42. Термостат ЭТК", + "type": "Writing", + "subtype": "ThermostatETK", + "id": "Thermo", + "widget": "anydataTmp", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "iv_k": 1, + "outside_id": "", + "num": 42, + "moduleName": "Thermostat" }, { - "name": "68. LCD экран 1602", - "type": "Reading", - "subtype": "Lcd2004", - "id": "Lcd", - "widget": "inputTxt", - "page": "screens", - "descr": "LCD Экран", - "addr": "0x27", - "size": "16,2", - "coord": "0,0", - "id2show": "", - "prefix": "", - "postfix": "", - "num": 68 + "global": 0, + "needSave": 0, + "name": "43. Термостат ЭТК2 ", + "type": "Writing", + "subtype": "ThermostatETK2", + "id": "Thermo", + "widget": "anydataTmp", + "page": "Климат", + "descr": "термостат", + "int": 60, + "round": 1, + "set_id": "", + "term_id": "", + "iv_k": 1, + "outside_id": "", + "num": 43, + "moduleName": "Thermostat" }, { - "global": 0, - "name": "69. OLED экран 64 8266", - "type": "Reading", - "subtype": "Oled64", - "id": "Oled", - "widget": "inputTxt", - "page": "screens", - "descr": "OLED Экран", - "addr": "0x3C", - "coord": "0,0", - "size": "1", - "id2show": "", - "prefix": "", - "postfix": "", - "num": 69 + "header": "screens" } ] \ No newline at end of file diff --git a/data_svelte/settings.json b/data_svelte/settings.json index 297019c9..46072a98 100644 --- a/data_svelte/settings.json +++ b/data_svelte/settings.json @@ -2,25 +2,33 @@ "name": "IoTmanagerVer4", "apssid": "IoTmanager", "appass": "", - "routerssid": "iot", - "routerpass": "hostel3333", + "routerssid": "HomeNET", + "routerpass": "Wi73Jktu0205", "timezone": 2, "ntp": "pool.ntp.org", "weblogin": "admin", "webpass": "admin", - "mqttServer": "", - "mqttPort": 8021, - "mqttPrefix": "/risenew", + "mqttServer": "192.168.88.10", + "mqttPort": 1883, + "mqttPrefix": "/IoTmanager", "mqttUser": "rise", "mqttPass": "3hostel3", "serverip": "http://iotmanager.org", + "serverlocal": "http://192.168.1.2:5500", "log": 0, "mqttin": 0, "i2c": 0, - "pinSCL": 0, - "pinSDA": 0, + "pinSCL": 9, + "pinSDA": 8, "i2cFreq": 100000, "wg": "group1", + "debugTraceMsgTlgrm": 1, "udps": 1, - "settings_": "" + "settings_": "", + "wifirep_apchanel": 7, + "wifirep_apip": "192.168.4.1", + "wifirep_staip": "192.168.1.160", + "wifirep_netmask": "255.255.255.0", + "wifirep_gateway": "192.168.4.1", + "wifirep_dns": "192.168.4.1" } \ No newline at end of file diff --git a/data_svelte/widgets.json b/data_svelte/widgets.json index 9da79005..ecd0a7e8 100644 --- a/data_svelte/widgets.json +++ b/data_svelte/widgets.json @@ -177,6 +177,39 @@ "maxCount": 86400, "type": "bar" }, + { + "name": "chart4", + "label": "График Часовой", + "widget": "chart", + "dateFormat": "HH:mm", + "maxCount": 3600, + "type": "bar" + }, + { + "name": "chart5", + "label": "График двойной", + "widget": "chart", + "series": [ + "Температура, С", + "Влажность, %" + ], + "dateFormat": "HH:mm", + "maxCount": 86400, + "pointRadius": 0 + }, + { + "name": "chart6", + "label": "График тройной", + "widget": "chart", + "series": [ + "Температура, С", + "Влажность, %", + "Давление, кПа" + ], + "dateFormat": "HH:mm", + "maxCount": 86400, + "pointRadius": 0 + }, { "name": "fillgauge", "label": "Бочка", @@ -321,7 +354,14 @@ "widget": "anydata", "after": "°", "icon": "speedometer" - }, + }, + { + "name": "anydataBar", + "label": "давление Bar", + "widget": "anydata", + "after": "Kg/cm²", + "icon": "speedometer" + }, { "name": "nil", "label": "Без виджета" diff --git a/include/Const.h b/include/Const.h index 599de170..a948a78b 100644 --- a/include/Const.h +++ b/include/Const.h @@ -2,7 +2,8 @@ #include "BuildTime.h" // Версия прошивки -#define FIRMWARE_VERSION 457 + +#define FIRMWARE_VERSION 462 #ifdef esp8266_1mb_ota #define FIRMWARE_NAME "esp8266_1mb_ota" @@ -32,6 +33,10 @@ #define FIRMWARE_NAME "esp32_4mb" #endif +#ifdef esp32_4mb3f +#define FIRMWARE_NAME "esp32_4mb3f" +#endif + #ifdef esp32cam_4mb #define FIRMWARE_NAME "esp32cam_4mb" #endif @@ -52,6 +57,21 @@ #define FIRMWARE_NAME "esp32s3_16mb" #endif +#ifdef bk7231n +#define FIRMWARE_NAME "bk7231n" +#endif + +#ifdef esp32c6_4mb +#define FIRMWARE_NAME "esp32c6_4mb" +#endif + +#ifdef esp32c6_8mb +#define FIRMWARE_NAME "esp32c6_8mb" +#endif + +#ifdef esp32_wifirep +#define FIRMWARE_NAME "esp32_wifirep" +#endif // Размер буфера json #define JSON_BUFFER_SIZE 4096 // держим 2 кб не меняем @@ -72,8 +92,9 @@ WEB_SOCKETS_FRAME_SIZE создан для того что бы не загру #define STANDARD_WEB_SERVER #define STANDARD_WEB_SOCKETS +//#ifndef LIBRETINY #define UDP_ENABLED - +//#endif // #define REST_FILE_OPERATIONS #define MQTT_RECONNECT_INTERVAL 20000 @@ -87,26 +108,38 @@ WEB_SOCKETS_FRAME_SIZE создан для того что бы не загру #define MIN_DATETIME 1575158400 #define LEAP_YEAR(Y) (((1970 + Y) > 0) && !((1970 + Y) % 4) && (((1970 + Y) % 100) || !((1970 + Y) % 400))) +#ifdef LIBRETINY +//#define WIFI_ASYNC +#endif + +#if defined(ESP32) && !defined(esp32_wifirep) +#define WIFI_ASYNC +#endif + // задачи таскера enum TimerTask_t { WIFI_SCAN, WIFI_MQTT_CONNECTION_CHECK, +#ifdef WIFI_ASYNC + WIFI_CONN, +#endif TIME, - TIME_SYNC, - UPTIME, - UDP, // UDPP + // TIME_SYNC, // не используется + // UPTIME, // не используется + UDPt, // UDPP TIMES, // периодические секундные проверки PTASK, ST, + PiWS, END }; -// задачи которые надо протащить через loop -enum NotAsyncActions { - do_ZERO, - do_MQTTPARAMSCHANGED, - do_LAST, -}; +// задачи которые надо протащить через loop // не используется +// enum NotAsyncActions { +// do_ZERO, +// do_MQTTPARAMSCHANGED, +// do_LAST, +// }; // состояния обновления enum UpdateStates { UPDATE_COMPLETED, UPDATE_FAILED, PATH_ERROR }; @@ -114,7 +147,7 @@ enum UpdateStates { UPDATE_COMPLETED, UPDATE_FAILED, PATH_ERROR }; enum distination { TO_MQTT, TO_WS, - TO_MQTT_WS, + TO_MQTT_WS }; -#define WS_BROADCAST -1 +// #define WS_BROADCAST -1 // не используется diff --git a/include/DebugTrace.h b/include/DebugTrace.h new file mode 100644 index 00000000..26daa463 --- /dev/null +++ b/include/DebugTrace.h @@ -0,0 +1,34 @@ +#pragma once + +// В папке toolchchain с которым собирались +// (Для esp32 например %%USERPROFILE%/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin) +// из командной строки Windows (cmd) запустить файл c параметрами: +// xtensa-esp32-elf-addr2line.exe -pfiaC -e Путь_к_файлу/firmware.elf Стэк_адресов_из_сообщения +// %%USERPROFILE%/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin xtensa-esp32-elf-addr2line.exe -pfiaC -e .pio/build/esp32_4mb3f/firmware.elf Стэк_адресов +#include "Global.h" +#if defined(ESP32) && !defined(esp32c3m_4mb) && !defined(esp32c6_4mb) && !defined(esp32c6_8mb) +#define RESTART_DEBUG_INFO +#endif +#if defined(RESTART_DEBUG_INFO) +#define CONFIG_RESTART_DEBUG_STACK_DEPTH 15 +typedef struct { + size_t heap_total; + size_t heap_free; + size_t heap_free_min; + time_t heap_min_time; + uint32_t backtrace[CONFIG_RESTART_DEBUG_STACK_DEPTH]; +} re_restart_debug_t; +__NOINIT_ATTR static int8_t bootloop_panic_count; + +void IRAM_ATTR debugUpdate(); +#endif // RESTART_DEBUG_INFO + + + +extern "C" void __real_esp_panic_handler(void*); +void printDebugTrace(); +void sendDebugTraceAndFreeMemory(bool); + +void startWatchDog(); +//extern "C" bool verifyRollbackLater(); +void verifyFirmware(); \ No newline at end of file diff --git a/include/DeviceList.h b/include/DeviceList.h index 773492f9..c698f7e5 100644 --- a/include/DeviceList.h +++ b/include/DeviceList.h @@ -1,13 +1,15 @@ #pragma once #include "Global.h" -#ifdef ESP8266 +#if defined (ESP8266) || defined(LIBRETINY) // эта библиотека встроена в ядро #include "ESPAsyncUDP.h" -#else +#elif defined(ESP32) #include "AsyncUDP.h" #endif +#ifndef LIBRETINY extern AsyncUDP asyncUdp; +#endif extern const String getThisDevice(); extern void addThisDeviceToList(); diff --git a/include/ESPConfiguration.h b/include/ESPConfiguration.h index 1e185219..c795becd 100644 --- a/include/ESPConfiguration.h +++ b/include/ESPConfiguration.h @@ -6,4 +6,4 @@ extern std::list IoTItems; // вектор ссылок базово extern void configure(String path); void clearConfigure(); -extern IoTItem* myIoTItem; +// extern IoTItem* myIoTItem; // экономим память, используется в одном месте diff --git a/include/EspFileSystem.h b/include/EspFileSystem.h index 078f0f8d..332b1c25 100644 --- a/include/EspFileSystem.h +++ b/include/EspFileSystem.h @@ -15,6 +15,18 @@ extern FS* filesystem; #endif #endif +#if defined(LIBRETINY) +#include + +#include "LittleFS.h" + +#define FileFS LittleFS +#define FS_NAME "LittleFS_LT" +#define FILE_READ "r" +#define FILE_WRITE "w" +#define FILE_APPEND "a" +#endif + #ifdef ESP8266 #if USE_LITTLEFS #include "LittleFS.h" @@ -39,6 +51,7 @@ extern void globalVarsSync(); // extern String getParamsJson(); extern void syncSettingsFlashJson(); +void resetSettingsFlashByPanic(); extern void syncValuesFlashJson(); extern const String getChipId(); diff --git a/include/Global.h b/include/Global.h index f748a4ba..11c31daa 100644 --- a/include/Global.h +++ b/include/Global.h @@ -9,6 +9,16 @@ #include #include +#ifdef LIBRETINY +#include +#include +#include +#ifdef STANDARD_WEB_SERVER +#include +#endif +#include +#endif + #ifdef ESP32 #include "WiFi.h" #include @@ -22,6 +32,7 @@ #ifdef ASYNC_WEB_SERVER #include +#include "AsyncWebServer.h" #endif #ifdef STANDARD_WEB_SERVER @@ -50,16 +61,21 @@ #include "utils/StringUtils.h" #include "PeriodicTasks.h" #include "classes/IoTGpio.h" - +#include "classes/IoTDiscovery.h" /********************************************************************************************************************* *****************************************глобальные объекты классов*************************************************** **********************************************************************************************************************/ extern IoTGpio IoTgpio; +#ifdef mod_RtcDriver extern IoTItem* rtcItem; +#endif //extern IoTItem* camItem; extern IoTItem* tlgrmItem; extern IoTBench* benchLoadItem; extern IoTBench* benchTaskItem; +extern IoTDiscovery* HADiscovery; +extern IoTDiscovery* HOMEdDiscovery; + extern TickerScheduler ts; extern WiFiClient espClient; @@ -77,6 +93,9 @@ extern ESP8266HTTPUpdateServer httpUpdater; #ifdef ESP32 extern WebServer HTTP; #endif +#ifdef LIBRETINY +extern WebServer HTTP; +#endif #endif #ifdef STANDARD_WEB_SOCKETS @@ -108,6 +127,7 @@ extern int mqttPort; extern String mqttPrefix; extern String mqttUser; extern String mqttPass; +extern String nameId; extern unsigned long mqttUptime; extern unsigned long flashWriteNumber; @@ -146,6 +166,8 @@ extern Time_t _time_local; extern Time_t _time_utc; extern bool _time_isTrust; +#define WEBSOCKETS_CLIENT_MAX 5 +extern int8_t ws_clients[WEBSOCKETS_CLIENT_MAX]; // extern unsigned long loopPeriod; // extern DynamicJsonDocument settingsFlashJsonDoc; diff --git a/include/MqttClient.h b/include/MqttClient.h index f7614f94..9ab587dc 100644 --- a/include/MqttClient.h +++ b/include/MqttClient.h @@ -29,8 +29,8 @@ bool publishChartFileToMqtt(String path, String id, int maxCount); void publishWidgets(); void mqttCallback(char* topic, uint8_t* payload, size_t length); -void handleMqttStatus(bool send); -void handleMqttStatus(bool send, int state); +//void handleMqttStatus(bool send); +void handleMqttStatus(bool send, int state = -1); const String getStateStr(int e); diff --git a/include/StandWebServer.h b/include/StandWebServer.h index de8fa3d7..db177ca2 100644 --- a/include/StandWebServer.h +++ b/include/StandWebServer.h @@ -9,6 +9,10 @@ extern bool handleFileRead(String path); extern void handleFileUpload(); extern void handleFileDelete(); extern void handleFileCreate(); +extern void handleLocalOTA(); +extern void handleUpdateOTA(); +extern void handleCors(); +extern void handleLocalOTA_Handler(); extern void handleFileList(); //void printDirectory(File dir, String& out); extern void handleStatus(); diff --git a/include/UpgradeFirm.h b/include/UpgradeFirm.h index c7b8d120..92a52c97 100644 --- a/include/UpgradeFirm.h +++ b/include/UpgradeFirm.h @@ -3,7 +3,7 @@ // #include "Upgrade.h" #ifdef ESP8266 // #include "ESP8266.h" -#else +#elif ESP32 #include #endif @@ -22,7 +22,7 @@ extern bool upgradeFS(String path); extern bool upgradeBuild(String path); extern void restartEsp(); -extern const String getBinPath(String file); +extern const String getBinPath(); extern void putUserDataToRam(); extern void saveUserDataToFlash(); extern void saveUpdeteStatus(String key, int val); \ No newline at end of file diff --git a/include/WsServer.h b/include/WsServer.h index eed0ac78..804bde27 100644 --- a/include/WsServer.h +++ b/include/WsServer.h @@ -19,6 +19,6 @@ void periodicWsSend(); void sendFileToWsByFrames(const String& filename, const String& header, const String& json, int client_id, size_t frameSize); void sendStringToWs(const String& header, String& payload, int client_id); - +void disconnectWSClient(uint8_t client_id); void sendDeviceList(uint8_t num); int getNumWSClients(); \ No newline at end of file diff --git a/include/classes/IoTDiscovery.h b/include/classes/IoTDiscovery.h new file mode 100644 index 00000000..f8c9610a --- /dev/null +++ b/include/classes/IoTDiscovery.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include "Global.h" +#include "classes/IoTItem.h" + +class IoTDiscovery : public IoTItem +{ +public: + IoTDiscovery(const String ¶meters); + ~IoTDiscovery(); + +// inline bool isDiscoveryHomed() { return HOMEd; } + +// inline bool isDiscoveryHA() { return HA; } + + String HOMEdTopic = ""; + String HATopic = ""; + //String ChipId = ""; + + virtual void mqttSubscribeDiscovery(); + + virtual void publishStatusHOMEd(const String &topic, const String &data); + + + +protected: + boolean publishRetain(const String &topic, const String &data); + virtual void getlayoutHA(); + virtual void deleteFromHOMEd(); + virtual void getlayoutHOMEd(); + + //bool HOMEd = false; + //bool HA = false; + //String HOMEdTopic; + +}; \ No newline at end of file diff --git a/include/classes/IoTItem.h b/include/classes/IoTItem.h index 5ff1e0fe..012c920d 100644 --- a/include/classes/IoTItem.h +++ b/include/classes/IoTItem.h @@ -4,6 +4,7 @@ //#include "classes/IoTBench.h" class IoTBench; +class IoTDiscovery; struct IoTValue { float valD = 0; @@ -54,14 +55,17 @@ class IoTItem { bool enableDoByInt = true; virtual IoTGpio* getGpioDriver(); - virtual IoTItem* getRtcDriver(); //virtual IoTItem* getCAMDriver(); virtual IoTItem* getTlgrmDriver(); //virtual IoTBench* getBenchmark(); - virtual IoTBench*getBenchmarkTask(); - virtual IoTBench*getBenchmarkLoad(); + virtual IoTBench* getBenchmarkTask(); + virtual IoTBench* getBenchmarkLoad(); + virtual IoTDiscovery* getHADiscovery(); + virtual IoTDiscovery* getHOMEdDiscovery(); +#ifdef mod_RtcDriver + virtual IoTItem* getRtcDriver(); virtual unsigned long getRtcUnixTime(); - +#endif // делаем доступным модулям отправку сообщений в телеграм virtual void sendTelegramMsg(bool often, String msg); virtual void sendFoto(uint8_t *buf, uint32_t length, const String &name); diff --git a/include/classes/IoTUart.h b/include/classes/IoTUart.h index e45697ca..576f7b27 100644 --- a/include/classes/IoTUart.h +++ b/include/classes/IoTUart.h @@ -28,6 +28,8 @@ class IoTUart : public IoTItem { protected: #ifdef ESP8266 SoftwareSerial* _myUART; +#elif LIBRETINY + SerialClass* _myUART; #else Stream* _myUART; #endif diff --git a/include/utils/StringUtils.h b/include/utils/StringUtils.h index ea403cfb..62dca319 100644 --- a/include/utils/StringUtils.h +++ b/include/utils/StringUtils.h @@ -12,6 +12,8 @@ uint8_t hexStringToUint8(const String& hex); uint16_t hexStringToUint16(const String& hex); +uint32_t hexStringToUint32(const String& hex); + String selectToMarkerLast(String str, const String& found); String selectToMarker(String str, const String& found); @@ -40,7 +42,7 @@ boolean isDigitDotCommaStr(const String& str); String prettyBytes(size_t size); -String uint64ToString(uint64_t input, uint8_t base = 10); +String uint64ToStringIoTM(uint64_t input, uint8_t base = 10); void cleanString(String& str); @@ -49,3 +51,5 @@ unsigned char ChartoHex(char ch); std::vector splitStr(const String& str, const String& delimiter); bool strInVector(const String& str, const std::vector& vec); + +String getUtf8CharByIndex(const String& utf8str, int index); diff --git a/include/utils/WiFiUtils.h b/include/utils/WiFiUtils.h index 28524211..9e60a2cb 100644 --- a/include/utils/WiFiUtils.h +++ b/include/utils/WiFiUtils.h @@ -2,10 +2,25 @@ #include "Global.h" #include "MqttClient.h" + +void addPortMap(String TCP_UDP, String maddr, u16_t mport, String daddr, u16_t dport); + boolean isNetworkActive(); uint8_t getNumAPClients(); -void routerConnect(); bool startAPMode(); +#ifndef WIFI_ASYNC +void routerConnect(); boolean RouterFind(std::vector jArray); +#else +void handleScanResults(); +void WiFiUtilsItit(); +void connectToNextNetwork(); +void checkConnection(); +void ScanAsync(); + +#endif uint8_t RSSIquality(); -extern void wifiSignalInit(); +//extern void wifiSignalInit(); +#ifdef LIBRETINY +String httpGetString(HTTPClient &http); +#endif \ No newline at end of file diff --git a/iotm/esp32_4mb3f/400/firmware.bin b/iotm/esp32_4mb3f/400/firmware.bin new file mode 100644 index 00000000..9b2b3570 Binary files /dev/null and b/iotm/esp32_4mb3f/400/firmware.bin differ diff --git a/iotm/esp32_4mb3f/400/littlefs.bin b/iotm/esp32_4mb3f/400/littlefs.bin new file mode 100644 index 00000000..af2c5ddc Binary files /dev/null and b/iotm/esp32_4mb3f/400/littlefs.bin differ diff --git a/iotm/esp32_4mb3f/400/partitions.bin b/iotm/esp32_4mb3f/400/partitions.bin new file mode 100644 index 00000000..92742ca8 Binary files /dev/null and b/iotm/esp32_4mb3f/400/partitions.bin differ diff --git a/iotm/ver.json b/iotm/ver.json index 06766b23..1d3a3413 100644 --- a/iotm/ver.json +++ b/iotm/ver.json @@ -1,5 +1,5 @@ { - "esp8266_4mb": { + "esp32_4mb3f": { "0": "400" } } \ No newline at end of file diff --git a/lib/LT_WebSockets/.clang-format b/lib/LT_WebSockets/.clang-format new file mode 100644 index 00000000..e72c54b1 --- /dev/null +++ b/lib/LT_WebSockets/.clang-format @@ -0,0 +1,63 @@ +--- +BasedOnStyle: Google +AccessModifierOffset: '-2' +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: 'true' +AlignConsecutiveDeclarations: 'false' +AlignEscapedNewlines: Left +AlignTrailingComments: 'true' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: 'true' +AllowShortLoopsOnASingleLine: 'true' +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'true' +AlwaysBreakTemplateDeclarations: 'false' +BinPackParameters: 'true' +BreakAfterJavaFieldAnnotations: 'false' +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: 'false' +BreakBeforeTernaryOperators: 'false' +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: 'false' +ColumnLimit: '0' +CompactNamespaces: 'true' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' +ConstructorInitializerIndentWidth: '4' +ContinuationIndentWidth: '4' +Cpp11BracedListStyle: 'false' +DerivePointerAlignment: 'false' +FixNamespaceComments: 'true' +IndentCaseLabels: 'true' +IndentWidth: '4' +IndentWrappedFunctionNames: 'false' +JavaScriptQuotes: Single +JavaScriptWrapImports: 'false' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +MaxEmptyLinesToKeep: '1' +NamespaceIndentation: All +ObjCBlockIndentWidth: '4' +ObjCSpaceAfterProperty: 'false' +ObjCSpaceBeforeProtocolList: 'false' +PointerAlignment: Middle +SortIncludes: 'false' +SortUsingDeclarations: 'true' +SpaceAfterCStyleCast: 'false' +SpaceAfterTemplateKeyword: 'false' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeParens: Never +SpaceInEmptyParentheses: 'false' +SpacesBeforeTrailingComments: '4' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +TabWidth: '4' +UseTab: Never + +... diff --git a/lib/LT_WebSockets/.github/workflows/main.yml b/lib/LT_WebSockets/.github/workflows/main.yml new file mode 100644 index 00000000..d7bea656 --- /dev/null +++ b/lib/LT_WebSockets/.github/workflows/main.yml @@ -0,0 +1,186 @@ +name: CI +on: + schedule: + - cron: '0 0 * * 5' + push: + branches: [ master ] + pull_request: + branches: [ master ] + release: + types: [ published, created, edited ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + check_version_files: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: check version + run: | + $GITHUB_WORKSPACE/travis/version.py --check + + prepare_example_json: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: generate examples + id: set-matrix + run: | + source $GITHUB_WORKSPACE/travis/common.sh + cd $GITHUB_WORKSPACE + echo -en "::set-output name=matrix::" + echo -en "[" + + get_sketches_json_matrix arduino $GITHUB_WORKSPACE/examples/esp8266 esp8266 1.8.19 esp8266com:esp8266:generic:xtal=80,dbg=Serial1 + echo -en "," + + get_sketches_json_matrix arduino $GITHUB_WORKSPACE/examples/esp8266 esp8266 1.8.19 esp8266com:esp8266:generic:xtal=80,eesz=1M,FlashMode=qio,FlashFreq=80 + echo -en "," + + get_sketches_json_matrix arduino $GITHUB_WORKSPACE/examples/esp32 esp32 1.8.19 espressif:esp32:esp32:FlashFreq=80 + + echo -en "]" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + + prepare_ide: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + IDE_VERSION: [1.8.19] + env: + IDE_VERSION: ${{ matrix.IDE_VERSION }} + + steps: + - uses: actions/checkout@v2 + + - name: Get Date + id: get-date + run: | + echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" + shell: bash + + - uses: actions/cache@v2 + id: cache_all + with: + path: | + /home/runner/arduino_ide + /home/runner/Arduino + key: ${{ runner.os }}-${{ steps.get-date.outputs.date }}-${{ matrix.IDE_VERSION }} + + - name: download IDE + if: steps.cache_all.outputs.cache-hit != 'true' + run: | + wget http://downloads.arduino.cc/arduino-$IDE_VERSION-linux64.tar.xz -q + tar xf arduino-$IDE_VERSION-linux64.tar.xz + mv arduino-$IDE_VERSION $HOME/arduino_ide + + - name: download ArduinoJson + if: steps.cache_all.outputs.cache-hit != 'true' + run: | + mkdir -p $HOME/Arduino/libraries + wget https://github.com/bblanchon/ArduinoJson/archive/6.x.zip -q + unzip 6.x.zip + mv ArduinoJson-6.x $HOME/Arduino/libraries/ArduinoJson + + - name: download esp8266 + if: steps.cache_all.outputs.cache-hit != 'true' + run: | + source $GITHUB_WORKSPACE/travis/common.sh + get_core esp8266 + + - name: download esp32 + if: steps.cache_all.outputs.cache-hit != 'true' + run: | + source $GITHUB_WORKSPACE/travis/common.sh + get_core esp32 + + build: + needs: [prepare_ide, prepare_example_json] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.prepare_example_json.outputs.matrix) }} + env: + CPU: ${{ matrix.cpu }} + BOARD: ${{ matrix.board }} + IDE_VERSION: ${{ matrix.ideversion }} + SKETCH: ${{ matrix.sketch }} + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: actions/checkout@v2 + + - name: install libgtk2.0-0 + run: | + sudo apt-get install -y libgtk2.0-0 + + - name: Get Date + id: get-date + run: | + echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" + shell: bash + + - uses: actions/cache@v2 + id: cache_all + with: + path: | + /home/runner/arduino_ide + /home/runner/Arduino + key: ${{ runner.os }}-${{ steps.get-date.outputs.date }}-${{ matrix.ideversion }} + + - name: install python serial + if: matrix.cpu == 'esp32' + run: | + sudo pip3 install pyserial + sudo pip install pyserial +# sudo apt install python-is-python3 + + - name: start DISPLAY + run: | + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16 + export DISPLAY=:1.0 + sleep 3 + + - name: test IDE + run: | + export PATH="$HOME/arduino_ide:$PATH" + which arduino + + - name: copy code + run: | + mkdir -p $HOME/Arduino/libraries/ + cp -r $GITHUB_WORKSPACE $HOME/Arduino/libraries/arduinoWebSockets + + - name: config IDE + run: | + export DISPLAY=:1.0 + export PATH="$HOME/arduino_ide:$PATH" + arduino --board $BOARD --save-prefs + arduino --get-pref sketchbook.path + arduino --pref update.check=false + + - name: build example + timeout-minutes: 20 + run: | + set -ex + export DISPLAY=:1.0 + export PATH="$HOME/arduino_ide:$PATH" + source $GITHUB_WORKSPACE/travis/common.sh + cd $GITHUB_WORKSPACE + build_sketch arduino $SKETCH + + done: + needs: [prepare_ide, prepare_example_json, build, check_version_files] + runs-on: ubuntu-latest + steps: + - name: Done + run: | + echo DONE diff --git a/lib/LT_WebSockets/.gitignore b/lib/LT_WebSockets/.gitignore new file mode 100644 index 00000000..267af0de --- /dev/null +++ b/lib/LT_WebSockets/.gitignore @@ -0,0 +1,37 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +/tests/webSocketServer/node_modules + +# IDE +.vscode +.cproject +.project +.settings +*.swp + diff --git a/lib/LT_WebSockets/.piopm b/lib/LT_WebSockets/.piopm new file mode 100644 index 00000000..3871c04b --- /dev/null +++ b/lib/LT_WebSockets/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "WebSockets", "version": "2.3.7", "spec": {"owner": "links2004", "id": 549, "name": "WebSockets", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/LT_WebSockets/.travis.yml b/lib/LT_WebSockets/.travis.yml new file mode 100644 index 00000000..6cdf5db4 --- /dev/null +++ b/lib/LT_WebSockets/.travis.yml @@ -0,0 +1,45 @@ +sudo: false +dist: + - xenial +addons: + apt: + packages: + - xvfb +language: bash +os: + - linux +env: + matrix: + - CPU="esp8266" BOARD="esp8266com:esp8266:generic:xtal=80" IDE_VERSION=1.6.13 + - CPU="esp8266" BOARD="esp8266com:esp8266:generic:xtal=80,dbg=Serial1" IDE_VERSION=1.6.13 + - CPU="esp8266" BOARD="esp8266com:esp8266:generic:xtal=80,eesz=1M,FlashMode=qio,FlashFreq=80" IDE_VERSION=1.8.13 + - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.8.5 + - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.8.9 + - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.8.13 +script: + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16 + - export DISPLAY=:1.0 + - sleep 3 + - wget http://downloads.arduino.cc/arduino-$IDE_VERSION-linux64.tar.xz + - tar xf arduino-$IDE_VERSION-linux64.tar.xz + - mv arduino-$IDE_VERSION $HOME/arduino_ide + - export PATH="$HOME/arduino_ide:$PATH" + - which arduino + - mkdir -p $HOME/Arduino/libraries + + - wget https://github.com/bblanchon/ArduinoJson/archive/6.x.zip + - unzip 6.x.zip + - mv ArduinoJson-6.x $HOME/Arduino/libraries/ArduinoJson + - cp -r $TRAVIS_BUILD_DIR $HOME/Arduino/libraries/arduinoWebSockets + - source $TRAVIS_BUILD_DIR/travis/common.sh + - get_core $CPU + - cd $TRAVIS_BUILD_DIR + - arduino --board $BOARD --save-prefs + - arduino --get-pref sketchbook.path + - arduino --pref update.check=false + - build_sketches arduino $HOME/Arduino/libraries/arduinoWebSockets/examples/$CPU $CPU + +notifications: + email: + on_success: change + on_failure: change diff --git a/lib/LT_WebSockets/LICENSE b/lib/LT_WebSockets/LICENSE new file mode 100644 index 00000000..f166cc57 --- /dev/null +++ b/lib/LT_WebSockets/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/lib/LT_WebSockets/README.md b/lib/LT_WebSockets/README.md new file mode 100644 index 00000000..ae2497f0 --- /dev/null +++ b/lib/LT_WebSockets/README.md @@ -0,0 +1,102 @@ +WebSocket Server and Client for Arduino [![Build Status](https://github.com/Links2004/arduinoWebSockets/workflows/CI/badge.svg?branch=master)](https://github.com/Links2004/arduinoWebSockets/actions?query=workflow%3ACI+branch%3Amaster) +=========================================== + +a WebSocket Server and Client for Arduino based on RFC6455. + + +##### Supported features of RFC6455 ##### + - text frame + - binary frame + - connection close + - ping + - pong + - continuation frame + +##### Limitations ##### + - max input length is limited to the ram size and the ```WEBSOCKETS_MAX_DATA_SIZE``` define + - max output length has no limit (the hardware is the limit) + - Client send big frames with mask 0x00000000 (on AVR all frames) + - continuation frame reassembly need to be handled in the application code + + ##### Limitations for Async ##### + - Functions called from within the context of the websocket event might not honor `yield()` and/or `delay()`. See [this issue](https://github.com/Links2004/arduinoWebSockets/issues/58#issuecomment-192376395) for more info and a potential workaround. + - wss / SSL is not possible. + +##### Supported Hardware ##### + - ESP8266 [Arduino for ESP8266](https://github.com/esp8266/Arduino/) + - ESP32 [Arduino for ESP32](https://github.com/espressif/arduino-esp32) + - ESP31B + - Particle with STM32 ARM Cortex M3 + - ATmega328 with Ethernet Shield (ATmega branch) + - ATmega328 with enc28j60 (ATmega branch) + - ATmega2560 with Ethernet Shield (ATmega branch) + - ATmega2560 with enc28j60 (ATmega branch) + +###### Note: ###### + + version 2.0.0 and up is not compatible with AVR/ATmega, check ATmega branch. + + version 2.3.0 has API changes for the ESP8266 BareSSL (may brakes existing code) + + Arduino for AVR not supports std namespace of c++. + +### wss / SSL ### + supported for: + - wss client on the ESP8266 + - wss / SSL is not natively supported in WebSocketsServer however it is possible to achieve secure websockets + by running the device behind an SSL proxy. See [Nginx](examples/Nginx/esp8266.ssl.reverse.proxy.conf) for a + sample Nginx server configuration file to enable this. + +### ESP Async TCP ### + +This libary can run in Async TCP mode on the ESP. + +The mode can be activated in the ```WebSockets.h``` (see WEBSOCKETS_NETWORK_TYPE define). + +[ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) libary is required. + + +### High Level Client API ### + + - `begin` : Initiate connection sequence to the websocket host. +```c++ +void begin(const char *host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); +void begin(String host, uint16_t port, String url = "/", String protocol = "arduino"); + ``` + - `onEvent`: Callback to handle for websocket events + + ```c++ + void onEvent(WebSocketClientEvent cbEvent); + ``` + + - `WebSocketClientEvent`: Handler for websocket events + ```c++ + void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length) + ``` +Where `WStype_t type` is defined as: + ```c++ + typedef enum { + WStype_ERROR, + WStype_DISCONNECTED, + WStype_CONNECTED, + WStype_TEXT, + WStype_BIN, + WStype_FRAGMENT_TEXT_START, + WStype_FRAGMENT_BIN_START, + WStype_FRAGMENT, + WStype_FRAGMENT_FIN, + WStype_PING, + WStype_PONG, + } WStype_t; + ``` + +### Issues ### +Submit issues to: https://github.com/Links2004/arduinoWebSockets/issues + +[![Join the chat at https://gitter.im/Links2004/arduinoWebSockets](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Links2004/arduinoWebSockets?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +### License and credits ### + +The library is licensed under [LGPLv2.1](https://github.com/Links2004/arduinoWebSockets/blob/master/LICENSE) + +[libb64](http://libb64.sourceforge.net/) written by Chris Venter. It is distributed under Public Domain see [LICENSE](https://github.com/Links2004/arduinoWebSockets/blob/master/src/libb64/LICENSE). diff --git a/lib/LT_WebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf b/lib/LT_WebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf new file mode 100644 index 00000000..ec5aa89f --- /dev/null +++ b/lib/LT_WebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf @@ -0,0 +1,83 @@ +# ESP8266 nginx SSL reverse proxy configuration file (tested and working on nginx v1.10.0) + +# proxy cache location +proxy_cache_path /opt/etc/nginx/cache levels=1:2 keys_zone=ESP8266_cache:10m max_size=10g inactive=5m use_temp_path=off; + +# webserver proxy +server { + + # general server parameters + listen 50080; + server_name myDomain.net; + access_log /opt/var/log/nginx/myDomain.net.access.log; + + # SSL configuration + ssl on; + ssl_certificate /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/fullchain.pem; + ssl_certificate_key /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/privkey.pem; + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; + ssl_prefer_server_ciphers on; + + location / { + + # proxy caching configuration + proxy_cache ESP8266_cache; + proxy_cache_revalidate on; + proxy_cache_min_uses 1; + proxy_cache_use_stale off; + proxy_cache_lock on; + # proxy_cache_bypass $http_cache_control; + # include the sessionId cookie value as part of the cache key - keeps the cache per user + # proxy_cache_key $proxy_host$request_uri$cookie_sessionId; + + # header pass through configuration + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # ESP8266 custom headers which identify to the device that it's running through an SSL proxy + proxy_set_header X-SSL On; + proxy_set_header X-SSL-WebserverPort 50080; + proxy_set_header X-SSL-WebsocketPort 50081; + + # extra debug headers + add_header X-Proxy-Cache $upstream_cache_status; + add_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # actual proxying configuration + proxy_ssl_session_reuse on; + # target the IP address of the device with proxy_pass + proxy_pass http://192.168.0.20; + proxy_read_timeout 90; + } + } + +# websocket proxy +server { + + # general server parameters + listen 50081; + server_name myDomain.net; + access_log /opt/var/log/nginx/myDomain.net.wss.access.log; + + # SSL configuration + ssl on; + ssl_certificate /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/fullchain.pem; + ssl_certificate_key /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/privkey.pem; + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; + ssl_prefer_server_ciphers on; + + location / { + + # websocket upgrade tunnel configuration + proxy_pass http://192.168.0.20:81; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_read_timeout 86400; + } + } diff --git a/lib/LT_WebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino b/lib/LT_WebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino new file mode 100644 index 00000000..9d49d149 --- /dev/null +++ b/lib/LT_WebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino @@ -0,0 +1,84 @@ +/* + * WebSocketClientAVR.ino + * + * Created on: 10.12.2015 + * + */ + +#include + +#include +#include + +#include + + + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; + +// Set the static IP address to use if the DHCP fails to assign +IPAddress ip(192, 168, 0, 177); + +WebSocketsClient webSocket; + + + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + + switch(type) { + case WStype_DISCONNECTED: + Serial.println("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + Serial.print("[WSc] Connected to url: "); + Serial.println((char *)payload); + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + Serial.print("[WSc] get text: "); + Serial.println((char *)payload); + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + Serial.print("[WSc] get binary length: "); + Serial.println(length); + // hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() +{ + // Open serial communications and wait for port to open: + Serial.begin(115200); + while (!Serial) {} + + // start the Ethernet connection: + if (Ethernet.begin(mac) == 0) { + Serial.println("Failed to configure Ethernet using DHCP"); + // no point in carrying on, so do nothing forevermore: + // try to congifure using IP address instead of DHCP: + Ethernet.begin(mac, ip); + } + + webSocket.begin("192.168.0.123", 8011); + webSocket.onEvent(webSocketEvent); + +} + + +void loop() +{ + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino b/lib/LT_WebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino new file mode 100644 index 00000000..5e5ead46 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino @@ -0,0 +1,110 @@ +/* + * WebSocketClient.ino + * + * Created on: 24.05.2015 + * + */ + +#include + +#include +#include +#include + +#include + + +WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial1 + +void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { + const uint8_t* src = (const uint8_t*) mem; + USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); + for(uint32_t i = 0; i < len; i++) { + if(i % cols == 0) { + USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); + } + USE_SERIAL.printf("%02X ", *src); + src++; + } + USE_SERIAL.printf("\n"); +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // server address, port and URL + webSocket.begin("192.168.0.123", 81, "/"); + + // event handler + webSocket.onEvent(webSocketEvent); + + // use HTTP Basic Authorization this is optional remove if not needed + webSocket.setAuthorization("user", "Password"); + + // try ever 5000 again if connection has failed + webSocket.setReconnectInterval(5000); + +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino b/lib/LT_WebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino new file mode 100644 index 00000000..9d722427 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino @@ -0,0 +1,106 @@ +/* + * WebSocketClientSSL.ino + * + * Created on: 10.12.2015 + * + * note SSL is only possible with the ESP8266 + * + */ + +#include + +#include +#include +#include + +#include + + +WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial1 + +void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { + const uint8_t* src = (const uint8_t*) mem; + USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); + for(uint32_t i = 0; i < len; i++) { + if(i % cols == 0) { + USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); + } + USE_SERIAL.printf("%02X ", *src); + src++; + } + USE_SERIAL.printf("\n"); +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.beginSSL("192.168.0.123", 81); + webSocket.onEvent(webSocketEvent); + +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp32/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino b/lib/LT_WebSockets/examples/esp32/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino new file mode 100644 index 00000000..af3572f9 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp32/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino @@ -0,0 +1,155 @@ +/* + * WebSocketClientSocketIOack.ino + * + * Created on: 20.07.2019 + * + */ + +#include + +#include +#include +#include + +#include + +#include +#include + +WiFiMulti WiFiMulti; +SocketIOclient socketIO; + +#define USE_SERIAL Serial + + +void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) { + switch(type) { + case sIOtype_DISCONNECT: + USE_SERIAL.printf("[IOc] Disconnected!\n"); + break; + case sIOtype_CONNECT: + USE_SERIAL.printf("[IOc] Connected to url: %s\n", payload); + + // join default namespace (no auto join in Socket.IO V3) + socketIO.send(sIOtype_CONNECT, "/"); + break; + case sIOtype_EVENT: + { + char * sptr = NULL; + int id = strtol((char *)payload, &sptr, 10); + USE_SERIAL.printf("[IOc] get event: %s id: %d\n", payload, id); + if(id) { + payload = (uint8_t *)sptr; + } + DynamicJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, payload, length); + if(error) { + USE_SERIAL.print(F("deserializeJson() failed: ")); + USE_SERIAL.println(error.c_str()); + return; + } + + String eventName = doc[0]; + USE_SERIAL.printf("[IOc] event name: %s\n", eventName.c_str()); + + // Message Includes a ID for a ACK (callback) + if(id) { + // creat JSON message for Socket.IO (ack) + DynamicJsonDocument docOut(1024); + JsonArray array = docOut.to(); + + // add payload (parameters) for the ack (callback function) + JsonObject param1 = array.createNestedObject(); + param1["now"] = millis(); + + // JSON to String (serializion) + String output; + output += id; + serializeJson(docOut, output); + + // Send event + socketIO.send(sIOtype_ACK, output); + } + } + break; + case sIOtype_ACK: + USE_SERIAL.printf("[IOc] get ack: %u\n", length); + break; + case sIOtype_ERROR: + USE_SERIAL.printf("[IOc] get error: %u\n", length); + break; + case sIOtype_BINARY_EVENT: + USE_SERIAL.printf("[IOc] get binary: %u\n", length); + break; + case sIOtype_BINARY_ACK: + USE_SERIAL.printf("[IOc] get binary ack: %u\n", length); + break; + } +} + +void setup() { + //USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + String ip = WiFi.localIP().toString(); + USE_SERIAL.printf("[SETUP] WiFi Connected %s\n", ip.c_str()); + + // server address, port and URL + socketIO.begin("10.11.100.100", 8880, "/socket.io/?EIO=4"); + + // event handler + socketIO.onEvent(socketIOEvent); +} + +unsigned long messageTimestamp = 0; +void loop() { + socketIO.loop(); + + uint64_t now = millis(); + + if(now - messageTimestamp > 2000) { + messageTimestamp = now; + + // creat JSON message for Socket.IO (event) + DynamicJsonDocument doc(1024); + JsonArray array = doc.to(); + + // add evnet name + // Hint: socket.on('event_name', .... + array.add("event_name"); + + // add payload (parameters) for the event + JsonObject param1 = array.createNestedObject(); + param1["now"] = (uint32_t) now; + + // JSON to String (serializion) + String output; + serializeJson(doc, output); + + // Send event + socketIO.sendEVENT(output); + + // Print JSON for debugging + USE_SERIAL.println(output); + } +} diff --git a/lib/LT_WebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino b/lib/LT_WebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino new file mode 100644 index 00000000..3e0d4f5b --- /dev/null +++ b/lib/LT_WebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino @@ -0,0 +1,104 @@ +/* + * WebSocketServer.ino + * + * Created on: 22.05.2015 + * + */ + +#include + +#include +#include +#include + +#include + +WiFiMulti WiFiMulti; +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial1 + +void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { + const uint8_t* src = (const uint8_t*) mem; + USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); + for(uint32_t i = 0; i < len; i++) { + if(i % cols == 0) { + USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); + } + USE_SERIAL.printf("%02X ", *src); + src++; + } + USE_SERIAL.printf("\n"); +} + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: + { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.begin(); + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino new file mode 100644 index 00000000..5ee489cd --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino @@ -0,0 +1,106 @@ +/* + * WebSocketClient.ino + * + * Created on: 24.05.2015 + * + */ + +#include + +#include +#include + +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial1 + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + case WStype_PING: + // pong will be send automatically + USE_SERIAL.printf("[WSc] get ping\n"); + break; + case WStype_PONG: + // answer to a ping we send + USE_SERIAL.printf("[WSc] get pong\n"); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // server address, port and URL + webSocket.begin("192.168.0.123", 81, "/"); + + // event handler + webSocket.onEvent(webSocketEvent); + + // use HTTP Basic Authorization this is optional remove if not needed + webSocket.setAuthorization("user", "Password"); + + // try ever 5000 again if connection has failed + webSocket.setReconnectInterval(5000); + + // start heartbeat (optional) + // ping server every 15000 ms + // expect pong from server within 3000 ms + // consider connection disconnected if pong is not received 2 times + webSocket.enableHeartbeat(15000, 3000, 2); + +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/README.md b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/README.md new file mode 100644 index 00000000..496eef25 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/README.md @@ -0,0 +1,27 @@ +## Minimal example of WebsocketClientOTA and Python server + +Take this as small example, how achieve OTA update on ESP8266 and ESP32. + +Python server was wrote from train so take it only as bare example. +It's working, but it's not mean to run in production. + + +### Usage: + +Start server: +```bash +cd python_ota_server +python3 -m venv .venv +source .venv/bin/activate +pip3 install -r requirements.txt +python3 main.py +``` + +Flash ESP with example sketch and start it. + +Change version inside example sketch to higher and compile it and save it to bin file. + +Rename it to `mydevice-1.0.1-esp8266.bin` and place it inside new folder firmware (server create it). + +When the ESP connect to server, it check if version flashed is equal to fw in firmware folder. If higher FW version is present, +start the flash process. \ No newline at end of file diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/WebSocketClientOTA.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/WebSocketClientOTA.ino new file mode 100644 index 00000000..2c87c251 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/WebSocketClientOTA.ino @@ -0,0 +1,263 @@ +/* + * WebSocketClientOTA.ino + * + * Created on: 25.10.2021 + * + */ + +#include +#include + +#ifdef ESP8266 + #include + #include + #include +#endif +#ifdef ESP32 + #include "WiFi.h" + #include "ESPmDNS.h" + #include +#endif + +#include +#include + +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial + +// Variables: +// Settable: +const char *version = "1.0.0"; +const char *name = "mydevice"; + +// Others: +#ifdef ESP8266 + const char *chip = "esp8266"; +#endif +#ifdef ESP32 + const char *chip = "esp32"; +#endif + +uint32_t maxSketchSpace = 0; +int SketchSize = 0; +bool ws_conn = false; + +String IpAddress2String(const IPAddress& ipAddress) +{ + return String(ipAddress[0]) + String(".") + + String(ipAddress[1]) + String(".") + + String(ipAddress[2]) + String(".") + + String(ipAddress[3]); +} + +void greetings_(){ + StaticJsonDocument<200> doc; + doc["type"] = "greetings"; + doc["mac"] = WiFi.macAddress(); + doc["ip"] = IpAddress2String(WiFi.localIP()); + doc["version"] = version; + doc["name"] = name; + doc["chip"] = chip; + + char data[200]; + serializeJson(doc, data); + webSocket.sendTXT(data); +} + +void register_(){ + StaticJsonDocument<200> doc; + doc["type"] = "register"; + doc["mac"] = WiFi.macAddress(); + + char data[200]; + serializeJson(doc, data); + webSocket.sendTXT(data); + ws_conn = true; +} + +typedef void (*CALLBACK_FUNCTION)(JsonDocument &msg); + +typedef struct { + char type[50]; + CALLBACK_FUNCTION func; +} RESPONSES_STRUCT; + +void OTA(JsonDocument &msg){ + USE_SERIAL.print(F("[WSc] OTA mode: ")); + const char* go = "go"; + const char* ok = "ok"; + if(strncmp( msg["value"], go, strlen(go)) == 0 ) { + USE_SERIAL.print(F("go\n")); + SketchSize = int(msg["size"]); + maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + USE_SERIAL.printf("[WSc] Max sketch size: %u\n", maxSketchSpace); + USE_SERIAL.printf("[WSc] Sketch size: %d\n", SketchSize); + USE_SERIAL.setDebugOutput(true); + if (!Update.begin(maxSketchSpace)) { //start with max available size + Update.printError(Serial); + ESP.restart(); + } + } else if (strncmp( msg["value"], ok, strlen(ok)) == 0) { + USE_SERIAL.print(F("OK\n")); + register_(); + } else { + USE_SERIAL.print(F("unknown value : ")); + USE_SERIAL.print(msg["value"].as()); + USE_SERIAL.print(F("\n")); + } +} + +void STATE(JsonDocument &msg){ + // Do something with message +} + +RESPONSES_STRUCT responses[] = { + {"ota", OTA}, + {"state", STATE}, +}; + +void text(uint8_t * payload, size_t length){ + // Convert mesage to something usable + char msgch[length]; + for (unsigned int i = 0; i < length; i++) + { + USE_SERIAL.print((char)payload[i]); + msgch[i] = ((char)payload[i]); + } + msgch[length] = '\0'; + + // Parse Json + StaticJsonDocument<200> doc_in; + DeserializationError error = deserializeJson(doc_in, msgch); + + if (error) { + USE_SERIAL.print(F("deserializeJson() failed: ")); + USE_SERIAL.println(error.c_str()); + return; + } + + // Handle each TYPE of message + int b = 0; + + for( b=0 ; strlen(responses[b].type) ; b++ ) + { + if( strncmp(doc_in["type"], responses[b].type, strlen(responses[b].type)) == 0 ) { + responses[b].func(doc_in); + } + } +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + // webSocket.sendTXT("Connected"); + greetings_(); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + text(payload, length); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + // hexdump(payload, length); + if (Update.write(payload, length) != length) { + Update.printError(Serial); + ESP.restart(); + } + yield(); + SketchSize -= length; + USE_SERIAL.printf("[WSc] Sketch size left: %u\n", SketchSize); + if (SketchSize < 1){ + if (Update.end(true)) { //true to set the size to the current progress + USE_SERIAL.printf("Update Success: \nRebooting...\n"); + delay(5); + yield(); + ESP.restart(); + } else { + Update.printError(USE_SERIAL); + ESP.restart(); + } + USE_SERIAL.setDebugOutput(false); + } + + // send data to server + // webSocket.sendBIN(payload, length); + break; + case WStype_PING: + // pong will be send automatically + USE_SERIAL.printf("[WSc] get ping\n"); + break; + case WStype_PONG: + // answer to a ping we send + USE_SERIAL.printf("[WSc] get pong\n"); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.print(F("\nMAC: ")); + USE_SERIAL.println(WiFi.macAddress()); + USE_SERIAL.print(F("\nDevice: ")); + USE_SERIAL.println(name); + USE_SERIAL.printf("\nVersion: %s\n", version); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "PASS"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // server address, port and URL + webSocket.begin("10.0.1.5", 8081, "/"); + + // event handler + webSocket.onEvent(webSocketEvent); + + // use HTTP Basic Authorization this is optional remove if not needed + // webSocket.setAuthorization("USER", "PASS"); + + // try ever 5000 again if connection has failed + webSocket.setReconnectInterval(5000); + + // start heartbeat (optional) + // ping server every 15000 ms + // expect pong from server within 3000 ms + // consider connection disconnected if pong is not received 2 times + webSocket.enableHeartbeat(15000, 3000, 2); + +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/main.py b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/main.py new file mode 100644 index 00000000..7e7fba11 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/main.py @@ -0,0 +1,235 @@ +"""Minimal example of Python websocket server +handling OTA updates for ESP32 amd ESP8266 + +Check and upload of firmware works. +Register and state function are jus for example. +""" +# pylint: disable=W0703,E1101 +import asyncio +import copy +import json +import logging +import subprocess +import threading +import time +from os import listdir +from os.path import join as join_pth +from pathlib import Path + +import websockets +from packaging import version + +# Logger settings +logging.basicConfig(filename="ws_server.log") +Logger = logging.getLogger('WS-OTA') +Logger.addHandler(logging.StreamHandler()) +Logger.setLevel(logging.INFO) + +# Path to directory with FW +fw_path = join_pth(Path().absolute(), "firmware") + + +def create_path(path: str) -> None: + """Check if path exist or create it""" + Path(path).mkdir(parents=True, exist_ok=True) + + +def shell(command): + """Handle execution of shell commands""" + with subprocess.Popen(command, shell=True, + stdout=subprocess.PIPE, + universal_newlines=True + ) as process: + for stdout_line in iter(process.stdout.readline, ""): + Logger.debug(stdout_line) + process.stdout.close() + return_code = process.wait() + Logger.debug("Shell returned: %s", return_code) + + return process.returncode + return None + + +async def binary_send(websocket, fw_file): + """Read firmware file, divide it to chunks and send them""" + with open(fw_file, "rb") as binaryfile: + + while True: + chunk = binaryfile.read(2048) + if not chunk: + break + try: + await websocket.send(chunk) + except Exception as exception: + Logger.exception(exception) + return False + time.sleep(0.2) + + +def version_checker(name, vdev, vapp): + """Parse and compare FW version""" + + if version.parse(vdev) < version.parse(vapp): + Logger.info("Client(%s) version %s is smaller than %s: Go for update", name, vdev, vapp) + return True + Logger.info("Client(%s) version %s is greater or equal to %s: Not updating", name, vdev, vapp) + return False + + +class WsOtaHandler (threading.Thread): + """Thread handling ota update + + Runing ota directly from message would kill WS + as message bus would timeout. + """ + def __init__(self, name, message, websocket): + threading.Thread.__init__(self, daemon=True) + self.name = name + self.msg = message + self.websocket = websocket + + def run(self, ): + try: + asyncio.run(self.start_) + except Exception as exception: + Logger.exception(exception) + finally: + pass + + async def start_(self): + """Start _ota se asyncio future""" + msg_task = asyncio.ensure_future( + self._ota()) + + done, pending = await asyncio.wait( + [msg_task], + return_when=asyncio.FIRST_COMPLETED, + ) + Logger.info("WS Ota Handler done: %s", done) + for task in pending: + task.cancel() + + async def _ota(self): + """Check for new fw and update or pass""" + device_name = self.msg['name'] + device_chip = self.msg['chip'] + device_version = self.msg['version'] + fw_version = '' + fw_name = '' + fw_device = '' + + for filename in listdir(fw_path): + fw_info = filename.split("-") + fw_device = fw_info[0] + if fw_device == device_name: + fw_version = fw_info[1] + fw_name = filename + break + + if not fw_version: + Logger.info("Client(%s): No fw found!", device_name) + msg = '{"type": "ota", "value":"ok"}' + await self.websocket.send(msg) + return + + if not version_checker(device_name, device_version, fw_version): + return + + fw_file = join_pth(fw_path, fw_name) + if device_chip == 'esp8266' and not fw_file.endswith('.gz'): + # We can compress fw to make it smaller for upload + fw_cpress = fw_file + fw_file = fw_cpress + ".gz" + cpress = f"gzip -9 {fw_cpress}" + cstate = shell(cpress) + if cstate: + Logger.error("Cannot compress firmware: %s", fw_name) + return + + # Get size of fw + size = Path(fw_file).stat().st_size + + # Request ota mode + msg = '{"type": "ota", "value":"go", "size":' + str(size) + '}' + await self.websocket.send(msg) + + # send file by chunks trough websocket + await binary_send(self.websocket, fw_file) + + +async def _register(websocket, message): + mac = message.get('mac') + name = message.get('name') + Logger.info("Client(%s) mac: %s", name, mac) + # Some code + + response = {'response_type': 'registry', 'state': 'ok'} + await websocket.send(json.dumps(response)) + + +async def _state(websocket, message): + mac = message.get('mac') + name = message.get('name') + Logger.info("Client(%s) mac: %s", name, mac) + # Some code + + response = {'response_type': 'state', 'state': 'ok'} + await websocket.send(json.dumps(response)) + + +async def _unhandleld(websocket, msg): + Logger.info("Unhandled message from device: %s", str(msg)) + response = {'response_type': 'response', 'state': 'nok'} + await websocket.send(json.dumps(response)) + + +async def _greetings(websocket, message): + WsOtaHandler('thread_ota', copy.deepcopy(message), websocket).start() + + +async def message_received(websocket, message) -> None: + """Handle incoming messages + + Check if message contain json and run waned function + """ + switcher = {"greetings": _greetings, + "register": _register, + "state": _state + } + + if message[0:1] == "{": + try: + msg_json = json.loads(message) + except Exception as exception: + Logger.error(exception) + return + + type_ = msg_json.get('type') + name = msg_json.get('name') + func = switcher.get(type_, _unhandleld) + Logger.debug("Client(%s)said: %s", name, type_) + + try: + await func(websocket, msg_json) + except Exception as exception: + Logger.error(exception) + + +# pylint: disable=W0613 +async def ws_server(websocket, path) -> None: + """Run in cycle and wait for new messages""" + async for message in websocket: + await message_received(websocket, message) + + +async def main(): + """Server starter + + Normal user can bind only port nubers greater than 1024 + """ + async with websockets.serve(ws_server, "10.0.1.5", 8081): + await asyncio.Future() # run forever + + +create_path(fw_path) +asyncio.run(main()) diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/requirements.txt b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/requirements.txt new file mode 100644 index 00000000..4fc2553f --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/requirements.txt @@ -0,0 +1,2 @@ +packaging +websockets \ No newline at end of file diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino new file mode 100644 index 00000000..d45060e9 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino @@ -0,0 +1,88 @@ +/* + * WebSocketClientSSL.ino + * + * Created on: 10.12.2015 + * + * note SSL is only possible with the ESP8266 + * + */ + +#include + +#include +#include + +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + + +#define USE_SERIAL Serial1 + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.beginSSL("192.168.0.123", 81); + webSocket.onEvent(webSocketEvent); + +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientSSLWithCA/WebSocketClientSSLWithCA.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSSLWithCA/WebSocketClientSSLWithCA.ino new file mode 100644 index 00000000..214f5e61 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSSLWithCA/WebSocketClientSSLWithCA.ino @@ -0,0 +1,103 @@ +/* + * WebSocketClientSSLWithCA.ino + * + * Created on: 27.10.2019 + * + * note SSL is only possible with the ESP8266 + * + */ + +#include + +#include +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +WebSocketsClient webSocket; + +#define USE_SERIAL Serial1 + + +// Can be obtained with: +// openssl s_client -showcerts -connect echo.websocket.org:443 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + //When using BearSSL, client certificate and private key can be set: + //webSocket.setSSLClientCertKey(clientCert, clientPrivateKey); + //clientCert and clientPrivateKey can be of types (const char *, const char *) , or of types (BearSSL::X509List, BearSSL::PrivateKey) + + webSocket.beginSslWithCA("echo.websocket.org", 443, "/", ENDPOINT_CA_CERT); + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino new file mode 100644 index 00000000..5ceacea4 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino @@ -0,0 +1,128 @@ +/* + * WebSocketClientSocketIO.ino + * + * Created on: 06.06.2016 + * + */ + +#include + +#include +#include + +#include + +#include +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +SocketIOclient socketIO; + +#define USE_SERIAL Serial1 + +void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) { + switch(type) { + case sIOtype_DISCONNECT: + USE_SERIAL.printf("[IOc] Disconnected!\n"); + break; + case sIOtype_CONNECT: + USE_SERIAL.printf("[IOc] Connected to url: %s\n", payload); + + // join default namespace (no auto join in Socket.IO V3) + socketIO.send(sIOtype_CONNECT, "/"); + break; + case sIOtype_EVENT: + USE_SERIAL.printf("[IOc] get event: %s\n", payload); + break; + case sIOtype_ACK: + USE_SERIAL.printf("[IOc] get ack: %u\n", length); + hexdump(payload, length); + break; + case sIOtype_ERROR: + USE_SERIAL.printf("[IOc] get error: %u\n", length); + hexdump(payload, length); + break; + case sIOtype_BINARY_EVENT: + USE_SERIAL.printf("[IOc] get binary: %u\n", length); + hexdump(payload, length); + break; + case sIOtype_BINARY_ACK: + USE_SERIAL.printf("[IOc] get binary ack: %u\n", length); + hexdump(payload, length); + break; + } +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + // disable AP + if(WiFi.getMode() & WIFI_AP) { + WiFi.softAPdisconnect(true); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + String ip = WiFi.localIP().toString(); + USE_SERIAL.printf("[SETUP] WiFi Connected %s\n", ip.c_str()); + + // server address, port and URL + socketIO.begin("10.11.100.100", 8880, "/socket.io/?EIO=4"); + + // event handler + socketIO.onEvent(socketIOEvent); +} + +unsigned long messageTimestamp = 0; +void loop() { + socketIO.loop(); + + uint64_t now = millis(); + + if(now - messageTimestamp > 2000) { + messageTimestamp = now; + + // creat JSON message for Socket.IO (event) + DynamicJsonDocument doc(1024); + JsonArray array = doc.to(); + + // add evnet name + // Hint: socket.on('event_name', .... + array.add("event_name"); + + // add payload (parameters) for the event + JsonObject param1 = array.createNestedObject(); + param1["now"] = (uint32_t) now; + + // JSON to String (serializion) + String output; + serializeJson(doc, output); + + // Send event + socketIO.sendEVENT(output); + + // Print JSON for debugging + USE_SERIAL.println(output); + } +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino new file mode 100644 index 00000000..3e4f87e1 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino @@ -0,0 +1,165 @@ +/* + * WebSocketClientSocketIOack.ino + * + * Created on: 20.07.2019 + * + */ + +#include + +#include +#include + +#include + +#include +#include + +#include + +ESP8266WiFiMulti WiFiMulti; +SocketIOclient socketIO; + +#define USE_SERIAL Serial + + +void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) { + switch(type) { + case sIOtype_DISCONNECT: + USE_SERIAL.printf("[IOc] Disconnected!\n"); + break; + case sIOtype_CONNECT: + USE_SERIAL.printf("[IOc] Connected to url: %s\n", payload); + + // join default namespace (no auto join in Socket.IO V3) + socketIO.send(sIOtype_CONNECT, "/"); + break; + case sIOtype_EVENT: + { + char * sptr = NULL; + int id = strtol((char *)payload, &sptr, 10); + USE_SERIAL.printf("[IOc] get event: %s id: %d\n", payload, id); + if(id) { + payload = (uint8_t *)sptr; + } + DynamicJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, payload, length); + if(error) { + USE_SERIAL.print(F("deserializeJson() failed: ")); + USE_SERIAL.println(error.c_str()); + return; + } + + String eventName = doc[0]; + USE_SERIAL.printf("[IOc] event name: %s\n", eventName.c_str()); + + // Message Includes a ID for a ACK (callback) + if(id) { + // creat JSON message for Socket.IO (ack) + DynamicJsonDocument docOut(1024); + JsonArray array = docOut.to(); + + // add payload (parameters) for the ack (callback function) + JsonObject param1 = array.createNestedObject(); + param1["now"] = millis(); + + // JSON to String (serializion) + String output; + output += id; + serializeJson(docOut, output); + + // Send event + socketIO.send(sIOtype_ACK, output); + } + } + break; + case sIOtype_ACK: + USE_SERIAL.printf("[IOc] get ack: %u\n", length); + hexdump(payload, length); + break; + case sIOtype_ERROR: + USE_SERIAL.printf("[IOc] get error: %u\n", length); + hexdump(payload, length); + break; + case sIOtype_BINARY_EVENT: + USE_SERIAL.printf("[IOc] get binary: %u\n", length); + hexdump(payload, length); + break; + case sIOtype_BINARY_ACK: + USE_SERIAL.printf("[IOc] get binary ack: %u\n", length); + hexdump(payload, length); + break; + } +} + +void setup() { + //USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + // disable AP + if(WiFi.getMode() & WIFI_AP) { + WiFi.softAPdisconnect(true); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + //WiFi.disconnect(); + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + String ip = WiFi.localIP().toString(); + USE_SERIAL.printf("[SETUP] WiFi Connected %s\n", ip.c_str()); + + // server address, port and URL + socketIO.begin("10.11.100.100", 8880, "/socket.io/?EIO=4"); + + // event handler + socketIO.onEvent(socketIOEvent); +} + +unsigned long messageTimestamp = 0; +void loop() { + socketIO.loop(); + + uint64_t now = millis(); + + if(now - messageTimestamp > 2000) { + messageTimestamp = now; + + // creat JSON message for Socket.IO (event) + DynamicJsonDocument doc(1024); + JsonArray array = doc.to(); + + // add evnet name + // Hint: socket.on('event_name', .... + array.add("event_name"); + + // add payload (parameters) for the event + JsonObject param1 = array.createNestedObject(); + param1["now"] = (uint32_t) now; + + // JSON to String (serializion) + String output; + serializeJson(doc, output); + + // Send event + socketIO.sendEVENT(output); + + // Print JSON for debugging + USE_SERIAL.println(output); + } +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino new file mode 100644 index 00000000..a0eb011f --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino @@ -0,0 +1,149 @@ +/* + WebSocketClientStomp.ino + + Example for connecting and maintining a connection with a STOMP websocket connection. + In this example, we connect to a Spring application (see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html). + + Created on: 25.09.2017 + Author: Martin Becker , Contact: becker@informatik.uni-wuerzburg.de +*/ + +// PRE + +#define USE_SERIAL Serial + + +// LIBRARIES + +#include +#include + +#include +#include + + +// SETTINGS + +const char* wlan_ssid = "yourssid"; +const char* wlan_password = "somepassword"; + +const char* ws_host = "the.host.net"; +const int ws_port = 80; + +// URL for STOMP endpoint. +// For the default config of Spring's STOMP support, the default URL is "/socketentry/websocket". +const char* stompUrl = "/socketentry/websocket"; // don't forget the leading "/" !!! + + +// VARIABLES + +WebSocketsClient webSocket; + + +// FUNCTIONS + +/** + * STOMP messages need to be NULL-terminated (i.e., \0 or \u0000). + * However, when we send a String or a char[] array without specifying + * a length, the size of the message payload is derived by strlen() internally, + * thus dropping any NULL values appended to the "msg"-String. + * + * To solve this, we first convert the String to a NULL terminated char[] array + * via "c_str" and set the length of the payload to include the NULL value. + */ +void sendMessage(String & msg) { + webSocket.sendTXT(msg.c_str(), msg.length() + 1); +} + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch (type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + String msg = "CONNECT\r\naccept-version:1.1,1.0\r\nheart-beat:10000,10000\r\n\r\n"; + sendMessage(msg); + } + break; + case WStype_TEXT: + { + // ##################### + // handle STOMP protocol + // ##################### + + String text = (char*) payload; + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + if (text.startsWith("CONNECTED")) { + + // subscribe to some channels + + String msg = "SUBSCRIBE\nid:sub-0\ndestination:/user/queue/messages\n\n"; + sendMessage(msg); + delay(1000); + + // and send a message + + msg = "SEND\ndestination:/app/message\n\n{\"user\":\"esp\",\"message\":\"Hello!\"}"; + sendMessage(msg); + delay(1000); + + } else { + + // do something with messages + + } + + break; + } + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + + // setup serial + + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + // USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + + + // connect to WiFi + + USE_SERIAL.print("Logging into WLAN: "); Serial.print(wlan_ssid); Serial.print(" ..."); + WiFi.mode(WIFI_STA); + WiFi.begin(wlan_ssid, wlan_password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + USE_SERIAL.print("."); + } + USE_SERIAL.println(" success."); + USE_SERIAL.print("IP: "); USE_SERIAL.println(WiFi.localIP()); + + + // connect to websocket + webSocket.begin(ws_host, ws_port, stompUrl); + webSocket.setExtraHeaders(); // remove "Origin: file://" header because it breaks the connection with Spring's default websocket config + // webSocket.setExtraHeaders("foo: I am so funny\r\nbar: not"); // some headers, in case you feel funny + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino new file mode 100644 index 00000000..cb0c45be --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino @@ -0,0 +1,150 @@ +/* + WebSocketClientStompOverSockJs.ino + + Example for connecting and maintining a connection with a SockJS+STOMP websocket connection. + In this example, we connect to a Spring application (see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html). + + Created on: 18.07.2017 + Author: Martin Becker , Contact: becker@informatik.uni-wuerzburg.de +*/ + +// PRE + +#define USE_SERIAL Serial + + +// LIBRARIES + +#include +#include + +#include +#include + + +// SETTINGS + +const char* wlan_ssid = "yourssid"; +const char* wlan_password = "somepassword"; + +const char* ws_host = "the.host.net"; +const int ws_port = 80; + +// base URL for SockJS (websocket) connection +// The complete URL will look something like this(cf. http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-36): +// ws://://<3digits>//websocket +// For the default config of Spring's SockJS/STOMP support, the default base URL is "/socketentry/". +const char* ws_baseurl = "/socketentry/"; // don't forget leading and trailing "/" !!! + + +// VARIABLES + +WebSocketsClient webSocket; + + +// FUNCTIONS + +void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + + switch (type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + } + break; + case WStype_TEXT: + { + // ##################### + // handle SockJs+STOMP protocol + // ##################### + + String text = (char*) payload; + + USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + if (payload[0] == 'h') { + + USE_SERIAL.println("Heartbeat!"); + + } else if (payload[0] == 'o') { + + // on open connection + char *msg = "[\"CONNECT\\naccept-version:1.1,1.0\\nheart-beat:10000,10000\\n\\n\\u0000\"]"; + webSocket.sendTXT(msg); + + } else if (text.startsWith("a[\"CONNECTED")) { + + // subscribe to some channels + + char *msg = "[\"SUBSCRIBE\\nid:sub-0\\ndestination:/user/queue/messages\\n\\n\\u0000\"]"; + webSocket.sendTXT(msg); + delay(1000); + + // and send a message + + msg = "[\"SEND\\ndestination:/app/message\\n\\n{\\\"user\\\":\\\"esp\\\",\\\"message\\\":\\\"Hello!\\\"}\\u0000\"]"; + webSocket.sendTXT(msg); + delay(1000); + } + + break; + } + case WStype_BIN: + USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + } + +} + +void setup() { + + // setup serial + + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + // USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + + + // connect to WiFi + + USE_SERIAL.print("Logging into WLAN: "); Serial.print(wlan_ssid); Serial.print(" ..."); + WiFi.mode(WIFI_STA); + WiFi.begin(wlan_ssid, wlan_password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + USE_SERIAL.print("."); + } + USE_SERIAL.println(" success."); + USE_SERIAL.print("IP: "); USE_SERIAL.println(WiFi.localIP()); + + + // ##################### + // create socket url according to SockJS protocol (cf. http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-36) + // ##################### + String socketUrl = ws_baseurl; + socketUrl += random(0, 999); + socketUrl += "/"; + socketUrl += random(0, 999999); // should be a random string, but this works (see ) + socketUrl += "/websocket"; + + // connect to websocket + webSocket.begin(ws_host, ws_port, socketUrl); + webSocket.setExtraHeaders(); // remove "Origin: file://" header because it breaks the connection with Spring's default websocket config + // webSocket.setExtraHeaders("foo: I am so funny\r\nbar: not"); // some headers, in case you feel funny + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino new file mode 100644 index 00000000..1ac3002d --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino @@ -0,0 +1,86 @@ +/* + * WebSocketServer.ino + * + * Created on: 22.05.2015 + * + */ + +#include + +#include +#include +#include +#include + +ESP8266WiFiMulti WiFiMulti; + +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial1 + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: + { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.begin(); + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} + diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino new file mode 100644 index 00000000..5fed1a95 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino @@ -0,0 +1,132 @@ +/* + * WebSocketServerAllFunctionsDemo.ino + * + * Created on: 10.05.2018 + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define LED_RED 15 +#define LED_GREEN 12 +#define LED_BLUE 13 + +#define USE_SERIAL Serial + +ESP8266WiFiMulti WiFiMulti; + +ESP8266WebServer server(80); +WebSocketsServer webSocket = WebSocketsServer(81); + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + if(payload[0] == '#') { + // we get RGB data + + // decode rgb data + uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16); + + analogWrite(LED_RED, ((rgb >> 16) & 0xFF)); + analogWrite(LED_GREEN, ((rgb >> 8) & 0xFF)); + analogWrite(LED_BLUE, ((rgb >> 0) & 0xFF)); + } + + break; + } + +} + +void setup() { + //USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + + digitalWrite(LED_RED, 1); + digitalWrite(LED_GREEN, 1); + digitalWrite(LED_BLUE, 1); + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // start webSocket server + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + + if(MDNS.begin("esp8266")) { + USE_SERIAL.println("MDNS responder started"); + } + + // handle index + server.on("/", []() { + // send index.html + server.send(200, "text/html", "LED Control:

R:
G:
B:
"); + }); + + server.begin(); + + // Add service to MDNS + MDNS.addService("http", "tcp", 80); + MDNS.addService("ws", "tcp", 81); + + digitalWrite(LED_RED, 0); + digitalWrite(LED_GREEN, 0); + digitalWrite(LED_BLUE, 0); + +} + +unsigned long last_10sec = 0; +unsigned int counter = 0; + +void loop() { + unsigned long t = millis(); + webSocket.loop(); + server.handleClient(); + + if((t - last_10sec) > 10 * 1000) { + counter++; + bool ping = (counter % 2); + int i = webSocket.connectedClients(ping); + USE_SERIAL.printf("%d Connected websocket clients ping: %d\n", i, ping); + last_10sec = millis(); + } +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino new file mode 100644 index 00000000..84c9775d --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino @@ -0,0 +1,94 @@ +/* + * WebSocketServer.ino + * + * Created on: 22.05.2015 + * + */ + +#include + +#include +#include +#include +#include + +ESP8266WiFiMulti WiFiMulti; + +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial + +String fragmentBuffer = ""; + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + break; + case WStype_BIN: + USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + hexdump(payload, length); + break; + + // Fragmentation / continuation opcode handling + // case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT_TEXT_START: + fragmentBuffer = (char*)payload; + USE_SERIAL.printf("[%u] get start start of Textfragment: %s\n", num, payload); + break; + case WStype_FRAGMENT: + fragmentBuffer += (char*)payload; + USE_SERIAL.printf("[%u] get Textfragment : %s\n", num, payload); + break; + case WStype_FRAGMENT_FIN: + fragmentBuffer += (char*)payload; + USE_SERIAL.printf("[%u] get end of Textfragment: %s\n", num, payload); + USE_SERIAL.printf("[%u] full frame: %s\n", num, fragmentBuffer.c_str()); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + webSocket.begin(); + webSocket.onEvent(webSocketEvent); +} + +void loop() { + webSocket.loop(); +} + diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/WebSocketServerHooked.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/WebSocketServerHooked.ino new file mode 100644 index 00000000..e762626b --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/WebSocketServerHooked.ino @@ -0,0 +1,103 @@ +/* + * WebSocketServerHooked.ino + * + * Created on: 22.05.2015 + * Hooked on: 28.10.2020 + * + */ + +#include + +#include +#include +#include +#include +#include + +ESP8266WiFiMulti WiFiMulti; + +ESP8266WebServer server(80); +WebSockets4WebServer webSocket; + +#define USE_SERIAL Serial + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: + { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + } + +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + server.on("/", []() { + server.send(200, "text/plain", "I am a regular webserver on port 80!\r\n"); + server.send(200, "text/plain", "I am also a websocket server on '/ws' on the same port 80\r\n"); + }); + + server.addHook(webSocket.hookForWebserver("/ws", webSocketEvent)); + + server.begin(); + Serial.println("HTTP server started on port 80"); + Serial.println("WebSocket server started on the same port"); + Serial.printf("my network address is either 'arduinoWebsockets.local' (mDNS) or '%s'\n", WiFi.localIP().toString().c_str()); + + if (!MDNS.begin("arduinoWebsockets")) { + Serial.println("Error setting up MDNS responder!"); + } +} + +void loop() { + server.handleClient(); + webSocket.loop(); + MDNS.update(); +} diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/emu b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/emu new file mode 100644 index 00000000..867a8cd7 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/emu @@ -0,0 +1,20 @@ +#!/bin/sh + +# linux script to compile&run arduinoWebSockets in a mock environment + +if [ -z "$ESP8266ARDUINO" ]; then + echo "please set ESP8266ARDUINO env-var to where esp8266/arduino sits" + exit 1 +fi + +set -e + +where=$(pwd) + +cd $ESP8266ARDUINO/tests/host/ + +make -j FORCE32=0 \ + ULIBDIRS=../../libraries/Hash/:~/dev/proj/arduino/libraries/arduinoWebSockets \ + ${where}/WebSocketServerHooked + +valgrind ./bin/WebSocketServerHooked/WebSocketServerHooked -b "$@" diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/ws-testclient.py b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/ws-testclient.py new file mode 100644 index 00000000..546c7ff2 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHooked/ws-testclient.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +# python websocket client to test with +# emulator: server is at ws://127.0.0.1:9080/ws +# esp8266: server is at ws:///ws +# (uncomment the right line below) + +#uri = "ws://127.0.0.1:9080/ws" +uri = "ws://arduinoWebsockets.local/ws" + +import websocket +try: + import thread +except ImportError: + import _thread as thread +import time + +def on_message(ws, message): + print("message"); + print(message) + +def on_error(ws, error): + print("error") + print(error) + +def on_close(ws): + print("### closed ###") + +def on_open(ws): + print("opened") + def run(*args): + for i in range(3): + time.sleep(1) + ws.send("Hello %d" % i) + time.sleep(1) + ws.close() + print("thread terminating...") + thread.start_new_thread(run, ()) + + +if __name__ == "__main__": + websocket.enableTrace(True) + ws = websocket.WebSocketApp(uri, on_message = on_message, on_error = on_error, on_close = on_close) + ws.on_open = on_open + ws.run_forever() diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino new file mode 100644 index 00000000..8bc646c4 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino @@ -0,0 +1,86 @@ +/* + * WebSocketServerHttpHeaderValidation.ino + * + * Created on: 08.06.2016 + * + */ + +#include + +#include +#include +#include +#include + +ESP8266WiFiMulti WiFiMulti; + +WebSocketsServer webSocket = WebSocketsServer(81); + +#define USE_SERIAL Serial1 + +const unsigned long int validSessionId = 12345; //some arbitrary value to act as a valid sessionId + +/* + * Returns a bool value as an indicator to describe whether a user is allowed to initiate a websocket upgrade + * based on the value of a cookie. This function expects the rawCookieHeaderValue to look like this "sessionId=|" + */ +bool isCookieValid(String rawCookieHeaderValue) { + + if (rawCookieHeaderValue.indexOf("sessionId") != -1) { + String sessionIdStr = rawCookieHeaderValue.substring(rawCookieHeaderValue.indexOf("sessionId=") + 10, rawCookieHeaderValue.indexOf("|")); + unsigned long int sessionId = strtoul(sessionIdStr.c_str(), NULL, 10); + return sessionId == validSessionId; + } + return false; +} + +/* + * The WebSocketServerHttpHeaderValFunc delegate passed to webSocket.onValidateHttpHeader + */ +bool validateHttpHeader(String headerName, String headerValue) { + + //assume a true response for any headers not handled by this validator + bool valid = true; + + if(headerName.equalsIgnoreCase("Cookie")) { + //if the header passed is the Cookie header, validate it according to the rules in 'isCookieValid' function + valid = isCookieValid(headerValue); + } + + return valid; +} + +void setup() { + // USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //Serial.setDebugOutput(true); + USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + //connecting clients must supply a valid session cookie at websocket upgrade handshake negotiation time + const char * headerkeys[] = { "Cookie" }; + size_t headerKeyCount = sizeof(headerkeys) / sizeof(char*); + webSocket.onValidateHttpHeader(validateHttpHeader, headerkeys, headerKeyCount); + webSocket.begin(); +} + +void loop() { + webSocket.loop(); +} + diff --git a/lib/LT_WebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino b/lib/LT_WebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino new file mode 100644 index 00000000..8f32e753 --- /dev/null +++ b/lib/LT_WebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino @@ -0,0 +1,121 @@ +/* + * WebSocketServer_LEDcontrol.ino + * + * Created on: 26.11.2015 + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define LED_RED 15 +#define LED_GREEN 12 +#define LED_BLUE 13 + +#define USE_SERIAL Serial + + +ESP8266WiFiMulti WiFiMulti; + +ESP8266WebServer server(80); +WebSocketsServer webSocket = WebSocketsServer(81); + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: { + IPAddress ip = webSocket.remoteIP(num); + USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + case WStype_TEXT: + USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + if(payload[0] == '#') { + // we get RGB data + + // decode rgb data + uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16); + + analogWrite(LED_RED, ((rgb >> 16) & 0xFF)); + analogWrite(LED_GREEN, ((rgb >> 8) & 0xFF)); + analogWrite(LED_BLUE, ((rgb >> 0) & 0xFF)); + } + + break; + } + +} + +void setup() { + //USE_SERIAL.begin(921600); + USE_SERIAL.begin(115200); + + //USE_SERIAL.setDebugOutput(true); + + USE_SERIAL.println(); + USE_SERIAL.println(); + USE_SERIAL.println(); + + for(uint8_t t = 4; t > 0; t--) { + USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); + USE_SERIAL.flush(); + delay(1000); + } + + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + + digitalWrite(LED_RED, 1); + digitalWrite(LED_GREEN, 1); + digitalWrite(LED_BLUE, 1); + + WiFiMulti.addAP("SSID", "passpasspass"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // start webSocket server + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + + if(MDNS.begin("esp8266")) { + USE_SERIAL.println("MDNS responder started"); + } + + // handle index + server.on("/", []() { + // send index.html + server.send(200, "text/html", "LED Control:

R:
G:
B:
"); + }); + + server.begin(); + + // Add service to MDNS + MDNS.addService("http", "tcp", 80); + MDNS.addService("ws", "tcp", 81); + + digitalWrite(LED_RED, 0); + digitalWrite(LED_GREEN, 0); + digitalWrite(LED_BLUE, 0); + +} + +void loop() { + webSocket.loop(); + server.handleClient(); +} diff --git a/lib/LT_WebSockets/examples/particle/ParticleWebSocketClient/application.cpp b/lib/LT_WebSockets/examples/particle/ParticleWebSocketClient/application.cpp new file mode 100644 index 00000000..461228f3 --- /dev/null +++ b/lib/LT_WebSockets/examples/particle/ParticleWebSocketClient/application.cpp @@ -0,0 +1,46 @@ +/* To compile using make CLI, create a folder under \firmware\user\applications and copy application.cpp there. +* Then, copy src files under particleWebSocket folder. +*/ + +#include "application.h" +#include "particleWebSocket/WebSocketsClient.h" + +WebSocketsClient webSocket; + +void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) +{ + switch (type) + { + case WStype_DISCONNECTED: + Serial.printlnf("[WSc] Disconnected!"); + break; + case WStype_CONNECTED: + Serial.printlnf("[WSc] Connected to URL: %s", payload); + webSocket.sendTXT("Connected\r\n"); + break; + case WStype_TEXT: + Serial.printlnf("[WSc] get text: %s", payload); + break; + case WStype_BIN: + Serial.printlnf("[WSc] get binary length: %u", length); + break; + } +} + +void setup() +{ + Serial.begin(9600); + + WiFi.setCredentials("[SSID]", "[PASSWORD]", WPA2, WLAN_CIPHER_AES_TKIP); + WiFi.connect(); + + webSocket.begin("192.168.1.153", 85, "/ClientService/?variable=Test1212"); + webSocket.onEvent(webSocketEvent); +} + +void loop() +{ + webSocket.sendTXT("Hello world!"); + delay(500); + webSocket.loop(); +} diff --git a/lib/LT_WebSockets/library.json b/lib/LT_WebSockets/library.json new file mode 100644 index 00000000..0de54214 --- /dev/null +++ b/lib/LT_WebSockets/library.json @@ -0,0 +1,25 @@ +{ + "authors": [ + { + "maintainer": true, + "name": "Markus Sattler", + "url": "https://github.com/Links2004" + } + ], + "description": "WebSocket Server and Client for Arduino based on RFC6455", + "export": { + "exclude": [ + "tests" + ] + }, + "frameworks": "arduino", + "keywords": "wifi, http, web, server, client, websocket", + "license": "LGPL-2.1", + "name": "LT_WebSockets", + "platforms": "atmelavr, espressif8266, espressif32", + "repository": { + "type": "git", + "url": "https://github.com/Links2004/arduinoWebSockets.git" + }, + "version": "2.3.7" +} \ No newline at end of file diff --git a/lib/LT_WebSockets/library.properties b/lib/LT_WebSockets/library.properties new file mode 100644 index 00000000..c6c20a52 --- /dev/null +++ b/lib/LT_WebSockets/library.properties @@ -0,0 +1,9 @@ +name=LT_WebSockets +version=2.3.7 +author=Markus Sattler +maintainer=Markus Sattler +sentence=WebSockets for Arduino (Server + Client) +paragraph=use 2.x.x for ESP and 1.3 for AVR +category=Communication +url=https://github.com/Links2004/arduinoWebSockets +architectures=* diff --git a/lib/LT_WebSockets/src/SocketIOclient.cpp b/lib/LT_WebSockets/src/SocketIOclient.cpp new file mode 100644 index 00000000..bf611953 --- /dev/null +++ b/lib/LT_WebSockets/src/SocketIOclient.cpp @@ -0,0 +1,260 @@ +/* + * SocketIOclient.cpp + * + * Created on: May 12, 2018 + * Author: links + */ + +#include "WebSockets.h" +#include "WebSocketsClient.h" +#include "SocketIOclient.h" + +SocketIOclient::SocketIOclient() { +} + +SocketIOclient::~SocketIOclient() { +} + +void SocketIOclient::begin(const char * host, uint16_t port, const char * url, const char * protocol) { + WebSocketsClient::beginSocketIO(host, port, url, protocol); + WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5); + initClient(); +} + +void SocketIOclient::begin(String host, uint16_t port, String url, String protocol) { + WebSocketsClient::beginSocketIO(host, port, url, protocol); + WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5); + initClient(); +} +#if defined(HAS_SSL) +void SocketIOclient::beginSSL(const char * host, uint16_t port, const char * url, const char * protocol) { + WebSocketsClient::beginSocketIOSSL(host, port, url, protocol); + WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5); + initClient(); +} + +void SocketIOclient::beginSSL(String host, uint16_t port, String url, String protocol) { + WebSocketsClient::beginSocketIOSSL(host, port, url, protocol); + WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5); + initClient(); +} +#if defined(SSL_BARESSL) +void SocketIOclient::beginSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { + WebSocketsClient::beginSocketIOSSLWithCA(host, port, url, CA_cert, protocol); + WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5); + initClient(); +} + +void SocketIOclient::beginSSLWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) { + WebSocketsClient::beginSocketIOSSLWithCA(host, port, url, CA_cert, protocol); + WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5); + initClient(); +} + +void SocketIOclient::setSSLClientCertKey(const char * clientCert, const char * clientPrivateKey) { + WebSocketsClient::setSSLClientCertKey(clientCert, clientPrivateKey); +} + +void SocketIOclient::setSSLClientCertKey(BearSSL::X509List * clientCert, BearSSL::PrivateKey * clientPrivateKey) { + WebSocketsClient::setSSLClientCertKey(clientCert, clientPrivateKey); +} + +#endif +#endif + +void SocketIOclient::configureEIOping(bool disableHeartbeat) { + _disableHeartbeat = disableHeartbeat; +} + +void SocketIOclient::initClient(void) { + if(_client.cUrl.indexOf("EIO=4") != -1) { + DEBUG_WEBSOCKETS("[wsIOc] found EIO=4 disable EIO ping on client\n"); + configureEIOping(true); + } +} + +/** + * set callback function + * @param cbEvent SocketIOclientEvent + */ +void SocketIOclient::onEvent(SocketIOclientEvent cbEvent) { + _cbEvent = cbEvent; +} + +bool SocketIOclient::isConnected(void) { + return WebSocketsClient::isConnected(); +} + +void SocketIOclient::setExtraHeaders(const char * extraHeaders) { + return WebSocketsClient::setExtraHeaders(extraHeaders); +} + +void SocketIOclient::setReconnectInterval(unsigned long time) { + return WebSocketsClient::setReconnectInterval(time); +} + +/** + * send text data to client + * @param num uint8_t client id + * @param type socketIOmessageType_t + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool SocketIOclient::send(socketIOmessageType_t type, uint8_t * payload, size_t length, bool headerToPayload) { + bool ret = false; + if(length == 0) { + length = strlen((const char *)payload); + } + if(clientIsConnected(&_client) && _client.status == WSC_CONNECTED) { + if(!headerToPayload) { + // webSocket Header + ret = WebSocketsClient::sendFrameHeader(&_client, WSop_text, length + 2, true); + // Engine.IO / Socket.IO Header + if(ret) { + uint8_t buf[3] = { eIOtype_MESSAGE, type, 0x00 }; + ret = WebSocketsClient::write(&_client, buf, 2); + } + if(ret && payload && length > 0) { + ret = WebSocketsClient::write(&_client, payload, length); + } + return ret; + } else { + // TODO implement + } + } + return false; +} + +bool SocketIOclient::send(socketIOmessageType_t type, const uint8_t * payload, size_t length) { + return send(type, (uint8_t *)payload, length); +} + +bool SocketIOclient::send(socketIOmessageType_t type, char * payload, size_t length, bool headerToPayload) { + return send(type, (uint8_t *)payload, length, headerToPayload); +} + +bool SocketIOclient::send(socketIOmessageType_t type, const char * payload, size_t length) { + return send(type, (uint8_t *)payload, length); +} + +bool SocketIOclient::send(socketIOmessageType_t type, String & payload) { + return send(type, (uint8_t *)payload.c_str(), payload.length()); +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool SocketIOclient::sendEVENT(uint8_t * payload, size_t length, bool headerToPayload) { + return send(sIOtype_EVENT, payload, length, headerToPayload); +} + +bool SocketIOclient::sendEVENT(const uint8_t * payload, size_t length) { + return sendEVENT((uint8_t *)payload, length); +} + +bool SocketIOclient::sendEVENT(char * payload, size_t length, bool headerToPayload) { + return sendEVENT((uint8_t *)payload, length, headerToPayload); +} + +bool SocketIOclient::sendEVENT(const char * payload, size_t length) { + return sendEVENT((uint8_t *)payload, length); +} + +bool SocketIOclient::sendEVENT(String & payload) { + return sendEVENT((uint8_t *)payload.c_str(), payload.length()); +} + +void SocketIOclient::loop(void) { + WebSocketsClient::loop(); + unsigned long t = millis(); + if(!_disableHeartbeat && (t - _lastHeartbeat) > EIO_HEARTBEAT_INTERVAL) { + _lastHeartbeat = t; + DEBUG_WEBSOCKETS("[wsIOc] send ping\n"); + WebSocketsClient::sendTXT(eIOtype_PING); + } +} + +void SocketIOclient::handleCbEvent(WStype_t type, uint8_t * payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + runIOCbEvent(sIOtype_DISCONNECT, NULL, 0); + DEBUG_WEBSOCKETS("[wsIOc] Disconnected!\n"); + break; + case WStype_CONNECTED: { + DEBUG_WEBSOCKETS("[wsIOc] Connected to url: %s\n", payload); + // send message to server when Connected + // Engine.io upgrade confirmation message (required) + WebSocketsClient::sendTXT("2probe"); + WebSocketsClient::sendTXT(eIOtype_UPGRADE); + runIOCbEvent(sIOtype_CONNECT, payload, length); + } break; + case WStype_TEXT: { + if(length < 1) { + break; + } + + engineIOmessageType_t eType = (engineIOmessageType_t)payload[0]; + switch(eType) { + case eIOtype_PING: + payload[0] = eIOtype_PONG; + DEBUG_WEBSOCKETS("[wsIOc] get ping send pong (%s)\n", payload); + WebSocketsClient::sendTXT(payload, length, false); + break; + case eIOtype_PONG: + DEBUG_WEBSOCKETS("[wsIOc] get pong\n"); + break; + case eIOtype_MESSAGE: { + if(length < 2) { + break; + } + socketIOmessageType_t ioType = (socketIOmessageType_t)payload[1]; + uint8_t * data = &payload[2]; + size_t lData = length - 2; + switch(ioType) { + case sIOtype_EVENT: + DEBUG_WEBSOCKETS("[wsIOc] get event (%d): %s\n", lData, data); + break; + case sIOtype_CONNECT: + DEBUG_WEBSOCKETS("[wsIOc] connected (%d): %s\n", lData, data); + return; + case sIOtype_DISCONNECT: + case sIOtype_ACK: + case sIOtype_ERROR: + case sIOtype_BINARY_EVENT: + case sIOtype_BINARY_ACK: + default: + DEBUG_WEBSOCKETS("[wsIOc] Socket.IO Message Type %c (%02X) is not implemented\n", ioType, ioType); + DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload); + break; + } + + runIOCbEvent(ioType, data, lData); + } break; + case eIOtype_OPEN: + case eIOtype_CLOSE: + case eIOtype_UPGRADE: + case eIOtype_NOOP: + default: + DEBUG_WEBSOCKETS("[wsIOc] Engine.IO Message Type %c (%02X) is not implemented\n", eType, eType); + DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload); + break; + } + } break; + case WStype_ERROR: + case WStype_BIN: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + case WStype_PING: + case WStype_PONG: + break; + } +} diff --git a/lib/LT_WebSockets/src/SocketIOclient.h b/lib/LT_WebSockets/src/SocketIOclient.h new file mode 100644 index 00000000..2eccea3d --- /dev/null +++ b/lib/LT_WebSockets/src/SocketIOclient.h @@ -0,0 +1,105 @@ +/** + * SocketIOclient.h + * + * Created on: May 12, 2018 + * Author: links + */ + +#ifndef SOCKETIOCLIENT_H_ +#define SOCKETIOCLIENT_H_ + +#include "WebSockets.h" +#include "WebSocketsClient.h" + +#define EIO_HEARTBEAT_INTERVAL 20000 + +#define EIO_MAX_HEADER_SIZE (WEBSOCKETS_MAX_HEADER_SIZE + 1) +#define SIO_MAX_HEADER_SIZE (EIO_MAX_HEADER_SIZE + 1) + +typedef enum { + eIOtype_OPEN = '0', ///< Sent from the server when a new transport is opened (recheck) + eIOtype_CLOSE = '1', ///< Request the close of this transport but does not shutdown the connection itself. + eIOtype_PING = '2', ///< Sent by the client. Server should answer with a pong packet containing the same data + eIOtype_PONG = '3', ///< Sent by the server to respond to ping packets. + eIOtype_MESSAGE = '4', ///< actual message, client and server should call their callbacks with the data + eIOtype_UPGRADE = '5', ///< Before engine.io switches a transport, it tests, if server and client can communicate over this transport. If this test succeed, the client sends an upgrade packets which requests the server to flush its cache on the old transport and switch to the new transport. + eIOtype_NOOP = '6', ///< A noop packet. Used primarily to force a poll cycle when an incoming websocket connection is received. +} engineIOmessageType_t; + +typedef enum { + sIOtype_CONNECT = '0', + sIOtype_DISCONNECT = '1', + sIOtype_EVENT = '2', + sIOtype_ACK = '3', + sIOtype_ERROR = '4', + sIOtype_BINARY_EVENT = '5', + sIOtype_BINARY_ACK = '6', +} socketIOmessageType_t; + +class SocketIOclient : protected WebSocketsClient { + public: +#ifdef __AVR__ + typedef void (*SocketIOclientEvent)(socketIOmessageType_t type, uint8_t * payload, size_t length); +#else + typedef std::function SocketIOclientEvent; +#endif + + SocketIOclient(void); + virtual ~SocketIOclient(void); + + void begin(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void begin(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); + +#ifdef HAS_SSL + void beginSSL(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void beginSSL(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); +#ifndef SSL_AXTLS + void beginSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * CA_cert = NULL, const char * protocol = "arduino"); + void beginSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", BearSSL::X509List * CA_cert = NULL, const char * protocol = "arduino"); + void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL); + void setSSLClientCertKey(BearSSL::X509List * clientCert = NULL, BearSSL::PrivateKey * clientPrivateKey = NULL); +#endif +#endif + bool isConnected(void); + + void onEvent(SocketIOclientEvent cbEvent); + + bool sendEVENT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool sendEVENT(const uint8_t * payload, size_t length = 0); + bool sendEVENT(char * payload, size_t length = 0, bool headerToPayload = false); + bool sendEVENT(const char * payload, size_t length = 0); + bool sendEVENT(String & payload); + + bool send(socketIOmessageType_t type, uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool send(socketIOmessageType_t type, const uint8_t * payload, size_t length = 0); + bool send(socketIOmessageType_t type, char * payload, size_t length = 0, bool headerToPayload = false); + bool send(socketIOmessageType_t type, const char * payload, size_t length = 0); + bool send(socketIOmessageType_t type, String & payload); + + void setExtraHeaders(const char * extraHeaders = NULL); + void setReconnectInterval(unsigned long time); + + void loop(void); + + void configureEIOping(bool disableHeartbeat = false); + + protected: + bool _disableHeartbeat = false; + uint64_t _lastHeartbeat = 0; + SocketIOclientEvent _cbEvent; + virtual void runIOCbEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(type, payload, length); + } + } + + void initClient(void); + + // Handeling events from websocket layer + virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) { + handleCbEvent(type, payload, length); + } + void handleCbEvent(WStype_t type, uint8_t * payload, size_t length); +}; + +#endif /* SOCKETIOCLIENT_H_ */ diff --git a/lib/LT_WebSockets/src/WebSockets.cpp b/lib/LT_WebSockets/src/WebSockets.cpp new file mode 100644 index 00000000..888981b4 --- /dev/null +++ b/lib/LT_WebSockets/src/WebSockets.cpp @@ -0,0 +1,776 @@ +/** + * @file WebSockets.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" + +#ifdef ESP8266 +#include +#endif + +extern "C" { +#if defined CORE_HAS_LIBB64 || defined LIBRETINY +#include +#else +#include "libb64/cencode_inc.h" +#endif +} + +#ifdef ESP8266 +#include +#elif defined(ESP32) +#include + +#if ESP_IDF_VERSION_MAJOR >= 4 +#if(ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(1, 0, 6)) +#include "sha/sha_parallel_engine.h" +#else +#include +#endif +#else +#include +#endif + +#else + +#if defined LIBRETINY +extern "C" { +#include +#include +} +#else +extern "C" { +#include "libsha1/libsha1.h" +} +#endif +#endif + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param code uint16_t see RFC + * @param reason ptr to the disconnect reason message + * @param reasonLen length of the disconnect reason message + */ +void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code); + if(client->status == WSC_CONNECTED && code) { + if(reason) { + sendFrame(client, WSop_close, (uint8_t *)reason, reasonLen); + } else { + uint8_t buffer[2]; + buffer[0] = ((code >> 8) & 0xFF); + buffer[1] = (code & 0xFF); + sendFrame(client, WSop_close, &buffer[0], 2); + } + } + clientDisconnect(client); +} + +/** + * + * @param buf uint8_t * ptr to the buffer for writing + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param mask bool add dummy mask to the frame (needed for web browser) + * @param maskkey uint8_t[4] key used for payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + */ +uint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) { + uint8_t headerSize; + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(mask) { + headerSize += 4; + } + + // create header + + // byte 0 + *headerPtr = 0x00; + if(fin) { + *headerPtr |= bit(7); ///< set Fin + } + *headerPtr |= opcode; ///< set opcode + headerPtr++; + + // byte 1 + *headerPtr = 0x00; + if(mask) { + *headerPtr |= bit(7); ///< set mask + } + + if(length < 126) { + *headerPtr |= length; + headerPtr++; + } else if(length < 0xFFFF) { + *headerPtr |= 126; + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } else { + // Normally we never get here (to less memory) + *headerPtr |= 127; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = ((length >> 24) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 16) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } + + if(mask) { + *headerPtr = maskKey[0]; + headerPtr++; + *headerPtr = maskKey[1]; + headerPtr++; + *headerPtr = maskKey[2]; + headerPtr++; + *headerPtr = maskKey[3]; + headerPtr++; + } + return headerSize; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @return true if ok + */ +bool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) { + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin); + + if(write(client, &buffer[0], headerSize) != headerSize) { + return false; + } + + return true; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * ptr to the payload + * @param length size_t length of the payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @param headerToPayload bool set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!) + * @return true if ok + */ +bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin, bool headerToPayload) { + if(client->tcp && !client->tcp->connected()) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num); + return false; + } + + if(client->status != WSC_CONNECTED) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not in WSC_CONNECTED state!?\n", client->num); + return false; + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send message frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\n", client->num, fin, opcode, client->cIsClient, length, headerToPayload); + + if(opcode == WSop_text) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, (payload + (headerToPayload ? 14 : 0))); + } + + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize; + uint8_t * headerPtr; + uint8_t * payloadPtr = payload; + bool useInternBuffer = false; + bool ret = true; + + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(client->cIsClient) { + headerSize += 4; + } + +#ifdef WEBSOCKETS_USE_BIG_MEM + // only for ESP since AVR has less HEAP + // try to send data in one TCP package (only if some free Heap is there) + if(!headerToPayload && ((length > 0) && (length < 1400)) && (GET_FREE_HEAP > 6000)) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] pack to one TCP package...\n", client->num); + uint8_t * dataPtr = (uint8_t *)malloc(length + WEBSOCKETS_MAX_HEADER_SIZE); + if(dataPtr) { + memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length); + headerToPayload = true; + useInternBuffer = true; + payloadPtr = dataPtr; + } + } +#endif + + // set Header Pointer + if(headerToPayload) { + // calculate offset in payload + headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize)); + } else { + headerPtr = &buffer[0]; + } + + if(client->cIsClient && useInternBuffer) { + // if we use a Intern Buffer we can modify the data + // by this fact its possible the do the masking + for(uint8_t x = 0; x < sizeof(maskKey); x++) { + maskKey[x] = random(0xFF); + } + } + + createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin); + + if(client->cIsClient && useInternBuffer) { + uint8_t * dataMaskPtr; + + if(headerToPayload) { + dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); + } else { + dataMaskPtr = payloadPtr; + } + + for(size_t x = 0; x < length; x++) { + dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); + } + } + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + if(headerToPayload) { + // header has be added to payload + // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings + // offset in payload is calculatetd 14 - headerSize + if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) { + ret = false; + } + } else { + // send header + if(write(client, &buffer[0], headerSize) != headerSize) { + ret = false; + } + + if(payloadPtr && length > 0) { + // send payload + if(write(client, &payloadPtr[0], length) != length) { + ret = false; + } + } + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] sending Frame Done (%luus).\n", client->num, (micros() - start)); + +#ifdef WEBSOCKETS_USE_BIG_MEM + if(useInternBuffer && payloadPtr) { + free(payloadPtr); + } +#endif + + return ret; +} + +/** + * callen when HTTP header is done + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::headerDone(WSclient_t * client) { + client->status = WSC_CONNECTED; + client->cWsRXsize = 0; + DEBUG_WEBSOCKETS("[WS][%d][headerDone] Header Handling Done.\n", client->num); +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->cHttpLine = ""; + handleWebsocket(client); +#endif +} + +/** + * handle the WebSocket stream + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::handleWebsocket(WSclient_t * client) { + if(client->cWsRXsize == 0) { + handleWebsocketCb(client); + } +} + +/** + * wait for + * @param client + * @param size + */ +bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) { + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + if(size > WEBSOCKETS_MAX_HEADER_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d too big!\n", client->num, size); + return false; + } + + if(client->cWsRXsize >= size) { + return true; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\n", client->num, size, client->cWsRXsize); + readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\n", client->num, size, ok); + if(ok) { + client->cWsRXsize = size; + server->handleWebsocketCb(client); + } else { + DEBUG_WEBSOCKETS("[WS][%d][readCb] failed.\n", client->num); + client->cWsRXsize = 0; + // timeout or error + server->clientDisconnect(client, 1002); + } + }, + this, size, std::placeholders::_1, std::placeholders::_2)); + return false; +} + +void WebSockets::handleWebsocketCb(WSclient_t * client) { + if(!client->tcp || !client->tcp->connected()) { + return; + } + + uint8_t * buffer = client->cWsHeader; + + WSMessageHeader_t * header = &client->cWsHeaderDecode; + uint8_t * payload = NULL; + + uint8_t headerLen = 2; + + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + // split first 2 bytes in the data + header->fin = ((*buffer >> 7) & 0x01); + header->rsv1 = ((*buffer >> 6) & 0x01); + header->rsv2 = ((*buffer >> 5) & 0x01); + header->rsv3 = ((*buffer >> 4) & 0x01); + header->opCode = (WSopcode_t)(*buffer & 0x0F); + buffer++; + + header->mask = ((*buffer >> 7) & 0x01); + header->payloadLen = (WSopcode_t)(*buffer & 0x7F); + buffer++; + + if(header->payloadLen == 126) { + headerLen += 2; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->payloadLen = buffer[0] << 8 | buffer[1]; + buffer += 2; + } else if(header->payloadLen == 127) { + headerLen += 8; + // read 64bit integer as length + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { + // really too big! + header->payloadLen = 0xFFFFFFFF; + } else { + header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; + } + buffer += 8; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, header->mask, header->payloadLen); + + if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload too big! (%u)\n", client->num, header->payloadLen); + clientDisconnect(client, 1009); + return; + } + + if(header->mask) { + headerLen += 4; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->maskKey = buffer; + buffer += 4; + } + + if(header->payloadLen > 0) { + // if text data we need one more + payload = (uint8_t *)malloc(header->payloadLen + 1); + + if(!payload) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, header->payloadLen); + clientDisconnect(client, 1011); + return; + } + readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); + } else { + handleWebsocketPayloadCb(client, true, NULL); + } +} + +void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) { + WSMessageHeader_t * header = &client->cWsHeaderDecode; + if(ok) { + if(header->payloadLen > 0) { + payload[header->payloadLen] = 0x00; + + if(header->mask) { + // decode XOR + for(size_t i = 0; i < header->payloadLen; i++) { + payload[i] = (payload[i] ^ header->maskKey[i % 4]); + } + } + } + + switch(header->opCode) { + case WSop_text: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload); + // no break here! + case WSop_binary: + case WSop_continuation: + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_ping: + // send pong back + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ping received (%s)\n", client->num, payload ? (const char *)payload : ""); + sendFrame(client, WSop_pong, payload, header->payloadLen); + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_pong: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char *)payload : ""); + client->pongReceived = true; + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_close: { +#ifndef NODEBUG_WEBSOCKETS + uint16_t reasonCode = 1000; + if(header->payloadLen >= 2) { + reasonCode = payload[0] << 8 | payload[1]; + } +#endif + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d\n", client->num, reasonCode); + if(header->payloadLen > 2) { + DEBUG_WEBSOCKETS(" (%s)\n", (payload + 2)); + } else { + DEBUG_WEBSOCKETS("\n"); + } + clientDisconnect(client, 1000); + } break; + default: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] got unknown opcode: %d\n", client->num, header->opCode); + clientDisconnect(client, 1002); + break; + } + + if(payload) { + free(payload); + } + + // reset input + client->cWsRXsize = 0; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + // register callback for next message + handleWebsocketWaitFor(client, 2); +#endif + + } else { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num); + free(payload); + clientDisconnect(client, 1002); + } +} + +/** + * generate the key for Sec-WebSocket-Accept + * @param clientKey String + * @return String Accept Key + */ +String WebSockets::acceptKey(String & clientKey) { + uint8_t sha1HashBin[20] = { 0 }; +#ifdef ESP8266 + sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); +#elif defined(ESP32) + String data = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + esp_sha(SHA1, (unsigned char *)data.c_str(), data.length(), &sha1HashBin[0]); +#else + clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + SHA1_CTX ctx; + SHA1Init(&ctx); + SHA1Update(&ctx, (const unsigned char *)clientKey.c_str(), clientKey.length()); + SHA1Final(&sha1HashBin[0], &ctx); +#endif + + String key = base64_encode(sha1HashBin, 20); + key.trim(); + + return key; +} + +/** + * base64_encode + * @param data uint8_t * + * @param length size_t + * @return base64 encoded String + */ +String WebSockets::base64_encode(uint8_t * data, size_t length) { + size_t size = ((length * 1.6f) + 1); + char * buffer = (char *)malloc(size); + if(buffer) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *)&data[0], length, &buffer[0], &_state); + len = base64_encode_blockend((buffer + len), &_state); + + String base64 = String(buffer); + free(buffer); + return base64; + } + return String("-FAIL-"); +} + +/** + * read x byte from tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return true if ok + */ +bool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) { +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + client->tcp->readBytes(out, n, std::bind([](WSclient_t * client, bool ok, WSreadWaitCb cb) { + if(cb) { + cb(client, ok); + } + }, + client, std::placeholders::_1, cb)); + +#else + unsigned long t = millis(); + ssize_t len; + DEBUG_WEBSOCKETS("[readCb] n: %zu t: %lu\n", n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[readCb] tcp is null!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[readCb] not connected!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[readCb] receive TIMEOUT! %lu\n", (millis() - t)); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->available()) { + WEBSOCKETS_YIELD_MORE(); + continue; + } + + len = client->tcp->read((uint8_t *)out, n); + if(len > 0) { + t = millis(); + out += len; + n -= len; + // DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } else { + // DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } + if(n > 0) { + WEBSOCKETS_YIELD(); + } + } + if(cb) { + cb(client, true); + } + WEBSOCKETS_YIELD(); +#endif + return true; +} + +/** + * write x byte to tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return bytes send + */ +size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) { + if(out == NULL) + return 0; + if(client == NULL) + return 0; + unsigned long t = millis(); + size_t len = 0; + size_t total = 0; + DEBUG_WEBSOCKETS("[write] n: %zu t: %lu\n", n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[write] tcp is null!\n"); + break; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[write] not connected!\n"); + break; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[write] write TIMEOUT! %lu\n", (millis() - t)); + break; + } + + len = client->tcp->write((const uint8_t *)out, n); + if(len) { + t = millis(); + out += len; + n -= len; + total += len; + // DEBUG_WEBSOCKETS("write %d left %d!\n", len, n); + } else { + DEBUG_WEBSOCKETS("WS write %d failed left %d!\n", len, n); + } + if(n > 0) { + WEBSOCKETS_YIELD(); + } + } + WEBSOCKETS_YIELD(); + return total; +} + +size_t WebSockets::write(WSclient_t * client, const char * out) { + if(client == NULL) + return 0; + if(out == NULL) + return 0; + return write(client, (uint8_t *)out, strlen(out)); +} + +/** + * enable ping/pong heartbeat process + * @param client WSclient_t * + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { + if(client == NULL) + return; + client->pingInterval = pingInterval; + client->pongTimeout = pongTimeout; + client->disconnectTimeoutCount = disconnectTimeoutCount; + client->pongReceived = false; +} + +/** + * handle ping/pong heartbeat timeout process + * @param client WSclient_t * + */ +void WebSockets::handleHBTimeout(WSclient_t * client) { + if(client->pingInterval) { // if heartbeat is enabled + uint32_t pi = millis() - client->lastPing; + + if(client->pongReceived) { + client->pongTimeoutCount = 0; + } else { + if(pi > client->pongTimeout) { // pong not received in time + client->pongTimeoutCount++; + client->lastPing = millis() - client->pingInterval - 500; // force ping on the next run + + DEBUG_WEBSOCKETS("[HBtimeout] pong TIMEOUT! lp=%d millis=%d pi=%d count=%d\n", client->lastPing, millis(), pi, client->pongTimeoutCount); + + if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { + DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); + clientDisconnect(client); + } + } + } + } +} + +#ifdef LIBRETINY +void randomSeed(unsigned long seed) +{ + if (seed != 0) { + srand(seed); + } +} +#endif diff --git a/lib/LT_WebSockets/src/WebSockets.h b/lib/LT_WebSockets/src/WebSockets.h new file mode 100644 index 00000000..9867d919 --- /dev/null +++ b/lib/LT_WebSockets/src/WebSockets.h @@ -0,0 +1,379 @@ +/** + * @file WebSockets.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETS_H_ +#define WEBSOCKETS_H_ + +#ifdef STM32_DEVICE +#include +#define bit(b) (1UL << (b)) // Taken directly from Arduino.h +#else +#include +#include +#endif + +#ifdef ARDUINO_ARCH_AVR +#error Version 2.x.x currently does not support Arduino with AVR since there is no support for std namespace of c++. +#error Use Version 1.x.x. (ATmega branch) +#else +#include +#endif + +#include "WebSocketsVersion.h" + +#ifndef NODEBUG_WEBSOCKETS +#ifdef DEBUG_ESP_PORT +#define DEBUG_WEBSOCKETS(...) \ + { \ + DEBUG_ESP_PORT.printf(__VA_ARGS__); \ + DEBUG_ESP_PORT.flush(); \ + } +#else +//#define DEBUG_WEBSOCKETS(...) os_printf( __VA_ARGS__ ) +#endif +#endif + +#ifndef DEBUG_WEBSOCKETS +#define DEBUG_WEBSOCKETS(...) +#ifndef NODEBUG_WEBSOCKETS +#define NODEBUG_WEBSOCKETS +#endif +#endif + +#if defined(ESP8266) || defined(ESP32) + +#define WEBSOCKETS_MAX_DATA_SIZE (15 * 1024) +#define WEBSOCKETS_USE_BIG_MEM +#define GET_FREE_HEAP ESP.getFreeHeap() +// moves all Header strings to Flash (~300 Byte) +//#define WEBSOCKETS_SAVE_RAM + +#if defined(ESP8266) +#define WEBSOCKETS_YIELD() delay(0) +#define WEBSOCKETS_YIELD_MORE() delay(1) +#elif defined(ESP32) +#define WEBSOCKETS_YIELD() yield() +#define WEBSOCKETS_YIELD_MORE() delay(1) +#endif + +#elif defined(STM32_DEVICE) + +#define WEBSOCKETS_MAX_DATA_SIZE (15 * 1024) +#define WEBSOCKETS_USE_BIG_MEM +#define GET_FREE_HEAP System.freeMemory() +#define WEBSOCKETS_YIELD() +#define WEBSOCKETS_YIELD_MORE() +#else + +// atmega328p has only 2KB ram! +#define WEBSOCKETS_MAX_DATA_SIZE (1024) +// moves all Header strings to Flash +#define WEBSOCKETS_SAVE_RAM +#define WEBSOCKETS_YIELD() +#define WEBSOCKETS_YIELD_MORE() +#endif + +#define WEBSOCKETS_TCP_TIMEOUT (5000) + +#define NETWORK_ESP8266_ASYNC (0) +#define NETWORK_ESP8266 (1) +#define NETWORK_W5100 (2) +#define NETWORK_ENC28J60 (3) +#define NETWORK_ESP32 (4) +#define NETWORK_ESP32_ETH (5) + +// max size of the WS Message Header +#define WEBSOCKETS_MAX_HEADER_SIZE (14) + +#if !defined(WEBSOCKETS_NETWORK_TYPE) +// select Network type based +#if defined(ESP8266) || defined(ESP31B) +#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266 +//#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266_ASYNC +//#define WEBSOCKETS_NETWORK_TYPE NETWORK_W5100 + +#elif defined(ESP32) +#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP32 +//#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP32_ETH +#elif defined(LIBRETINY) +#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266 +#else +#define WEBSOCKETS_NETWORK_TYPE NETWORK_W5100 + +#endif +#endif + +// Includes and defined based on Network Type +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + +// Note: +// No SSL/WSS support for client in Async mode +// TLS lib need a sync interface! + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#include +#define SSL_AXTLS +#elif defined(ESP31B) +#include +#else +#error "network type ESP8266 ASYNC only possible on the ESP mcu!" +#endif + +#include +#include +#define WEBSOCKETS_NETWORK_CLASS AsyncTCPbuffer +#define WEBSOCKETS_NETWORK_SERVER_CLASS AsyncServer + + +#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) && !defined(LIBRETINY) +#if !defined(ESP8266) && !defined(ESP31B) +#error "network type ESP8266 only possible on the ESP mcu!" +#endif + +#if defined(ESP8266) +#include +#if defined(wificlientbearssl_h) && !defined(USING_AXTLS) && !defined(wificlientsecure_h) +#define SSL_BARESSL +#else +#define SSL_AXTLS +#endif +#else +#include +#endif + +#define WEBSOCKETS_NETWORK_CLASS WiFiClient +#define WEBSOCKETS_NETWORK_SSL_CLASS WiFiClientSecure +#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer +#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_W5100) + +#ifdef STM32_DEVICE +#define WEBSOCKETS_NETWORK_CLASS TCPClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS TCPServer +#endif + +#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ENC28J60) + +#include +#define WEBSOCKETS_NETWORK_CLASS UIPClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS UIPServer + +#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + +#include +#include +#define SSL_AXTLS +#define WEBSOCKETS_NETWORK_CLASS WiFiClient +#define WEBSOCKETS_NETWORK_SSL_CLASS WiFiClientSecure +#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer + +#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32_ETH) + +#include +#define WEBSOCKETS_NETWORK_CLASS WiFiClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer + +#elif defined(LIBRETINY) +//#include +//#include +//#define WEBSOCKETS_NETWORK_CLASS EthernetClient +//#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer +//#if defined(LIBRETINY) +typedef struct SHA1Context SHA1_CTX; +#include +//#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266 +//#include +//#define SSL_AXTLS +#define WEBSOCKETS_NETWORK_CLASS WiFiClient +//#define WEBSOCKETS_NETWORK_SSL_CLASS WiFiClientSecure +#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer +//#endif +#else +#error "no network type selected!" +#endif + +#ifdef WEBSOCKETS_NETWORK_SSL_CLASS +#define HAS_SSL +#endif + +// moves all Header strings to Flash (~300 Byte) +#ifdef WEBSOCKETS_SAVE_RAM +#define WEBSOCKETS_STRING(var) F(var) +#else +#define WEBSOCKETS_STRING(var) var +#endif + +typedef enum { + WSC_NOT_CONNECTED, + WSC_HEADER, + WSC_BODY, + WSC_CONNECTED +} WSclientsStatus_t; + +typedef enum { + WStype_ERROR, + WStype_DISCONNECTED, + WStype_CONNECTED, + WStype_TEXT, + WStype_BIN, + WStype_FRAGMENT_TEXT_START, + WStype_FRAGMENT_BIN_START, + WStype_FRAGMENT, + WStype_FRAGMENT_FIN, + WStype_PING, + WStype_PONG, +} WStype_t; + +typedef enum { + WSop_continuation = 0x00, ///< %x0 denotes a continuation frame + WSop_text = 0x01, ///< %x1 denotes a text frame + WSop_binary = 0x02, ///< %x2 denotes a binary frame + ///< %x3-7 are reserved for further non-control frames + WSop_close = 0x08, ///< %x8 denotes a connection close + WSop_ping = 0x09, ///< %x9 denotes a ping + WSop_pong = 0x0A ///< %xA denotes a pong + ///< %xB-F are reserved for further control frames +} WSopcode_t; + +typedef struct { + bool fin; + bool rsv1; + bool rsv2; + bool rsv3; + + WSopcode_t opCode; + bool mask; + + size_t payloadLen; + + uint8_t * maskKey; +} WSMessageHeader_t; + +typedef struct { + void init(uint8_t num, + uint32_t pingInterval, + uint32_t pongTimeout, + uint8_t disconnectTimeoutCount) { + this->num = num; + this->pingInterval = pingInterval; + this->pongTimeout = pongTimeout; + this->disconnectTimeoutCount = disconnectTimeoutCount; + } + + uint8_t num = 0; ///< connection number + + WSclientsStatus_t status = WSC_NOT_CONNECTED; + + WEBSOCKETS_NETWORK_CLASS * tcp = nullptr; + + bool isSocketIO = false; ///< client for socket.io server + +#if defined(HAS_SSL) + bool isSSL = false; ///< run in ssl mode + WEBSOCKETS_NETWORK_SSL_CLASS * ssl; +#endif + + String cUrl; ///< http url + uint16_t cCode = 0; ///< http code + + bool cIsClient = false; ///< will be used for masking + bool cIsUpgrade = false; ///< Connection == Upgrade + bool cIsWebsocket = false; ///< Upgrade == websocket + + String cSessionId; ///< client Set-Cookie (session id) + String cKey; ///< client Sec-WebSocket-Key + String cAccept; ///< client Sec-WebSocket-Accept + String cProtocol; ///< client Sec-WebSocket-Protocol + String cExtensions; ///< client Sec-WebSocket-Extensions + uint16_t cVersion = 0; ///< client Sec-WebSocket-Version + + uint8_t cWsRXsize = 0; ///< State of the RX + uint8_t cWsHeader[WEBSOCKETS_MAX_HEADER_SIZE]; ///< RX WS Message buffer + WSMessageHeader_t cWsHeaderDecode; + + String base64Authorization; ///< Base64 encoded Auth request + String plainAuthorization; ///< Base64 encoded Auth request + + String extraHeaders; + + bool cHttpHeadersValid = false; ///< non-websocket http header validity indicator + size_t cMandatoryHeadersCount; ///< non-websocket mandatory http headers present count + + bool pongReceived = false; + uint32_t pingInterval = 0; // how often ping will be sent, 0 means "heartbeat is not active" + uint32_t lastPing = 0; // millis when last pong has been received + uint32_t pongTimeout = 0; // interval in millis after which pong is considered to timeout + uint8_t disconnectTimeoutCount = 0; // after how many subsequent pong timeouts discconnect will happen, 0 means "do not disconnect" + uint8_t pongTimeoutCount = 0; // current pong timeout count + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + String cHttpLine; ///< HTTP header lines +#endif + +} WSclient_t; + +class WebSockets { + protected: +#ifdef __AVR__ + typedef void (*WSreadWaitCb)(WSclient_t * client, bool ok); +#else + typedef std::function WSreadWaitCb; +#endif + + virtual void clientDisconnect(WSclient_t * client) = 0; + virtual bool clientIsConnected(WSclient_t * client) = 0; + + void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0); + + virtual void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) = 0; + + uint8_t createHeader(uint8_t * buf, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin); + bool sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length = 0, bool fin = true); + bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool fin = true, bool headerToPayload = false); + + void headerDone(WSclient_t * client); + + void handleWebsocket(WSclient_t * client); + + bool handleWebsocketWaitFor(WSclient_t * client, size_t size); + void handleWebsocketCb(WSclient_t * client); + void handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload); + + String acceptKey(String & clientKey); + String base64_encode(uint8_t * data, size_t length); + + bool readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb); + virtual size_t write(WSclient_t * client, uint8_t * out, size_t n); + size_t write(WSclient_t * client, const char * out); + + void enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); + void handleHBTimeout(WSclient_t * client); +}; + +#ifndef UNUSED +#define UNUSED(var) (void)(var) +#endif +#endif /* WEBSOCKETS_H_ */ diff --git a/lib/LT_WebSockets/src/WebSockets4WebServer.h b/lib/LT_WebSockets/src/WebSockets4WebServer.h new file mode 100644 index 00000000..a542f1ea --- /dev/null +++ b/lib/LT_WebSockets/src/WebSockets4WebServer.h @@ -0,0 +1,80 @@ +/** + * @file WebSocketsServer.cpp + * @date 28.10.2020 + * @author Markus Sattler & esp8266/arduino community + * + * Copyright (c) 2020 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __WEBSOCKETS4WEBSERVER_H +#define __WEBSOCKETS4WEBSERVER_H + +#include +#include + +#if WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK + +class WebSockets4WebServer : public WebSocketsServerCore { + public: + WebSockets4WebServer(const String & origin = "", const String & protocol = "arduino") + : WebSocketsServerCore(origin, protocol) { + begin(); + } + + ESP8266WebServer::HookFunction hookForWebserver(const String & wsRootDir, WebSocketServerEvent event) { + onEvent(event); + + return [&, wsRootDir](const String & method, const String & url, WiFiClient * tcpClient, ESP8266WebServer::ContentTypeFunction contentType) { + (void)contentType; + + if(!(method == "GET" && url.indexOf(wsRootDir) == 0)) { + return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE; + } + + // allocate a WiFiClient copy (like in WebSocketsServer::handleNewClients()) + WEBSOCKETS_NETWORK_CLASS * newTcpClient = new WEBSOCKETS_NETWORK_CLASS(*tcpClient); + + // Then initialize a new WSclient_t (like in WebSocketsServer::handleNewClient()) + WSclient_t * client = handleNewClient(newTcpClient); + + if(client) { + // give "GET " + String headerLine; + headerLine.reserve(url.length() + 5); + headerLine = "GET "; + headerLine += url; + handleHeader(client, &headerLine); + } + + // tell webserver to not close but forget about this client + return ESP8266WebServer::CLIENT_IS_GIVEN; + }; + } +}; +#else // WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK + +#ifndef WEBSERVER_HAS_HOOK +#error Your current Framework / Arduino core version does not support Webserver Hook Functions +#else +#error Your Hardware Platform does not support Webserver Hook Functions +#endif + +#endif // WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK + +#endif // __WEBSOCKETS4WEBSERVER_H diff --git a/lib/LT_WebSockets/src/WebSocketsClient.cpp b/lib/LT_WebSockets/src/WebSocketsClient.cpp new file mode 100644 index 00000000..60e38f5c --- /dev/null +++ b/lib/LT_WebSockets/src/WebSocketsClient.cpp @@ -0,0 +1,980 @@ +/** + * @file WebSocketsClient.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include "WebSocketsClient.h" + +WebSocketsClient::WebSocketsClient() { + _cbEvent = NULL; + _client.num = 0; + _client.cIsClient = true; + _client.extraHeaders = WEBSOCKETS_STRING("Origin: file://"); + _reconnectInterval = 500; + _port = 0; + _host = ""; +} + +WebSocketsClient::~WebSocketsClient() { + disconnect(); +} + +/** + * calles to init the Websockets server + */ +void WebSocketsClient::begin(const char * host, uint16_t port, const char * url, const char * protocol) { + _host = host; + _port = port; +#if defined(HAS_SSL) + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = NULL; +#endif + + _client.num = 0; + _client.status = WSC_NOT_CONNECTED; + _client.tcp = NULL; +#if defined(HAS_SSL) + _client.isSSL = false; + _client.ssl = NULL; +#endif + _client.cUrl = url; + _client.cCode = 0; + _client.cIsUpgrade = false; + _client.cIsWebsocket = true; + _client.cKey = ""; + _client.cAccept = ""; + _client.cProtocol = protocol; + _client.cExtensions = ""; + _client.cVersion = 0; + _client.base64Authorization = ""; + _client.plainAuthorization = ""; + _client.isSocketIO = false; + + _client.lastPing = 0; + _client.pongReceived = false; + _client.pongTimeoutCount = 0; + +#ifdef ESP8266 + randomSeed(RANDOM_REG32); +#else + // todo find better seed + randomSeed(millis()); +#endif +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + asyncConnect(); +#endif + + _lastConnectionFail = 0; + _lastHeaderSent = 0; + + DEBUG_WEBSOCKETS("[WS-Client] Websocket Version: " WEBSOCKETS_VERSION "\n"); +} + +void WebSocketsClient::begin(String host, uint16_t port, String url, String protocol) { + begin(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +#ifndef LIBRETINY +void WebSocketsClient::begin(IPAddress host, uint16_t port, const char * url, const char * protocol) { + return begin(host.toString().c_str(), port, url, protocol); +} +#endif + +#if defined(HAS_SSL) +#if defined(SSL_AXTLS) +void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const char * fingerprint, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = fingerprint; + _CA_cert = NULL; +} + +void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) { + beginSSL(host.c_str(), port, url.c_str(), fingerprint.c_str(), protocol.c_str()); +} + +void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = CA_cert; +} +#else +void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const uint8_t * fingerprint, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = fingerprint; + _CA_cert = NULL; +} + +void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = CA_cert; +} + +void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { + beginSslWithCA(host, port, url, new BearSSL::X509List(CA_cert), protocol); +} + +void WebSocketsClient::setSSLClientCertKey(BearSSL::X509List * clientCert, BearSSL::PrivateKey * clientPrivateKey) { + _client_cert = clientCert; + _client_key = clientPrivateKey; +} + +void WebSocketsClient::setSSLClientCertKey(const char * clientCert, const char * clientPrivateKey) { + setSSLClientCertKey(new BearSSL::X509List(clientCert), new BearSSL::PrivateKey(clientPrivateKey)); +} + +#endif // SSL_AXTLS +#endif // HAS_SSL + +void WebSocketsClient::beginSocketIO(const char * host, uint16_t port, const char * url, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; +} + +void WebSocketsClient::beginSocketIO(String host, uint16_t port, String url, String protocol) { + beginSocketIO(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +#if defined(HAS_SSL) +void WebSocketsClient::beginSocketIOSSL(const char * host, uint16_t port, const char * url, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; +} + +void WebSocketsClient::beginSocketIOSSL(String host, uint16_t port, String url, String protocol) { + beginSocketIOSSL(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +#if defined(SSL_BARESSL) +void WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = CA_cert; +} +#endif + +void WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; +#if defined(SSL_BARESSL) + _CA_cert = new BearSSL::X509List(CA_cert); +#else + _CA_cert = CA_cert; +#endif +} + +#endif + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * called in arduino loop + */ +void WebSocketsClient::loop(void) { + if(_port == 0) { + return; + } + WEBSOCKETS_YIELD(); + if(!clientIsConnected(&_client)) { + // do not flood the server + if((millis() - _lastConnectionFail) < _reconnectInterval) { + return; + } + +#if defined(HAS_SSL) + if(_client.isSSL) { + DEBUG_WEBSOCKETS("[WS-Client] connect wss...\n"); + if(_client.ssl) { + delete _client.ssl; + _client.ssl = NULL; + _client.tcp = NULL; + } + _client.ssl = new WEBSOCKETS_NETWORK_SSL_CLASS(); + _client.tcp = _client.ssl; + if(_CA_cert) { + DEBUG_WEBSOCKETS("[WS-Client] setting CA certificate"); +#if defined(ESP32) + _client.ssl->setCACert(_CA_cert); +#elif defined(ESP8266) && defined(SSL_AXTLS) + _client.ssl->setCACert((const uint8_t *)_CA_cert, strlen(_CA_cert) + 1); +#elif defined(ESP8266) && defined(SSL_BARESSL) + _client.ssl->setTrustAnchors(_CA_cert); +#else +#error setCACert not implemented +#endif +#if defined(ESP32) + } else if(!SSL_FINGERPRINT_IS_SET) { + _client.ssl->setInsecure(); +#elif defined(SSL_BARESSL) + } else if(SSL_FINGERPRINT_IS_SET) { + _client.ssl->setFingerprint(_fingerprint); + } else { + _client.ssl->setInsecure(); + } + if(_client_cert && _client_key) { + _client.ssl->setClientRSACert(_client_cert, _client_key); + DEBUG_WEBSOCKETS("[WS-Client] setting client certificate and key"); +#endif + } + } else { + DEBUG_WEBSOCKETS("[WS-Client] connect ws...\n"); + if(_client.tcp) { + delete _client.tcp; + _client.tcp = NULL; + } + _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); + } +#else + _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); +#endif + + if(!_client.tcp) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); + return; + } + WEBSOCKETS_YIELD(); +#if defined(ESP32) + if(_client.tcp->connect(_host.c_str(), _port, WEBSOCKETS_TCP_TIMEOUT)) { +#else + if(_client.tcp->connect(_host.c_str(), _port)) { +#endif + connectedCb(); + _lastConnectionFail = 0; + } else { + connectFailedCb(); + _lastConnectionFail = millis(); + } + } else { + handleClientData(); + WEBSOCKETS_YIELD(); + if(_client.status == WSC_CONNECTED) { + handleHBPing(); + handleHBTimeout(&_client); + } + } +} +#endif + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) { + _cbEvent = cbEvent; +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsClient::sendTXT(uint8_t * payload, size_t length, bool headerToPayload) { + if(length == 0) { + length = strlen((const char *)payload); + } + if(clientIsConnected(&_client)) { + return sendFrame(&_client, WSop_text, payload, length, true, headerToPayload); + } + return false; +} + +bool WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) { + return sendTXT((uint8_t *)payload, length); +} + +bool WebSocketsClient::sendTXT(char * payload, size_t length, bool headerToPayload) { + return sendTXT((uint8_t *)payload, length, headerToPayload); +} + +bool WebSocketsClient::sendTXT(const char * payload, size_t length) { + return sendTXT((uint8_t *)payload, length); +} + +bool WebSocketsClient::sendTXT(String & payload) { + return sendTXT((uint8_t *)payload.c_str(), payload.length()); +} + +bool WebSocketsClient::sendTXT(char payload) { + uint8_t buf[WEBSOCKETS_MAX_HEADER_SIZE + 2] = { 0x00 }; + buf[WEBSOCKETS_MAX_HEADER_SIZE] = payload; + return sendTXT(buf, 1, true); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsClient::sendBIN(uint8_t * payload, size_t length, bool headerToPayload) { + if(clientIsConnected(&_client)) { + return sendFrame(&_client, WSop_binary, payload, length, true, headerToPayload); + } + return false; +} + +bool WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) { + return sendBIN((uint8_t *)payload, length); +} + +/** + * sends a WS ping to Server + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsClient::sendPing(uint8_t * payload, size_t length) { + if(clientIsConnected(&_client)) { + bool sent = sendFrame(&_client, WSop_ping, payload, length); + if(sent) + _client.lastPing = millis(); + return sent; + } + return false; +} + +bool WebSocketsClient::sendPing(String & payload) { + return sendPing((uint8_t *)payload.c_str(), payload.length()); +} + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsClient::disconnect(void) { + if(clientIsConnected(&_client)) { + WebSockets::clientDisconnect(&_client, 1000); + } +} + +/** + * set the Authorizatio for the http request + * @param user const char * + * @param password const char * + */ +void WebSocketsClient::setAuthorization(const char * user, const char * password) { + if(user && password) { + String auth = user; + auth += ":"; + auth += password; + _client.base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length()); + } +} + +/** + * set the Authorizatio for the http request + * @param auth const char * base64 + */ +void WebSocketsClient::setAuthorization(const char * auth) { + if(auth) { + //_client.base64Authorization = auth; + _client.plainAuthorization = auth; + } +} + +/** + * set extra headers for the http request; + * separate headers by "\r\n" + * @param extraHeaders const char * extraHeaders + */ +void WebSocketsClient::setExtraHeaders(const char * extraHeaders) { + _client.extraHeaders = extraHeaders; +} + +/** + * set the reconnect Interval + * how long to wait after a connection initiate failed + * @param time in ms + */ +void WebSocketsClient::setReconnectInterval(unsigned long time) { + _reconnectInterval = time; +} + +bool WebSocketsClient::isConnected(void) { + return (_client.status == WSC_CONNECTED); +} + +//################################################################################# +//################################################################################# +//################################################################################# + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param length size_t + */ +void WebSocketsClient::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) { + WStype_t type = WStype_ERROR; + + UNUSED(client); + + switch(opcode) { + case WSop_text: + type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START; + break; + case WSop_binary: + type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START; + break; + case WSop_continuation: + type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT; + break; + case WSop_ping: + type = WStype_PING; + break; + case WSop_pong: + type = WStype_PONG; + break; + case WSop_close: + default: + break; + } + + runCbEvent(type, payload, length); +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::clientDisconnect(WSclient_t * client) { + bool event = false; + +#if defined(HAS_SSL) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + if(client->isSSL && client->ssl) { + if(client->ssl->connected()) { + client->ssl->flush(); + client->ssl->stop(); + } + event = true; + delete client->ssl; + client->ssl = NULL; + client->tcp = NULL; + } +#endif +#endif //ssl + if(client->tcp) { + if(client->tcp->connected()) { +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + client->tcp->flush(); +#endif + client->tcp->stop(); + } + event = true; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->status = WSC_NOT_CONNECTED; +#else + delete client->tcp; +#endif + client->tcp = NULL; + } + + client->cCode = 0; + client->cKey = ""; + client->cAccept = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + client->cSessionId = ""; + + client->status = WSC_NOT_CONNECTED; + _lastConnectionFail = millis(); + + DEBUG_WEBSOCKETS("[WS-Client] client disconnected.\n"); + if(event) { + runCbEvent(WStype_DISCONNECTED, NULL, 0); + } +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = conneted + */ +bool WebSocketsClient::clientIsConnected(WSclient_t * client) { + if(!client->tcp) { + return false; + } + + if(client->tcp->connected()) { + if(client->status != WSC_NOT_CONNECTED) { + return true; + } + } else { + // client lost + if(client->status != WSC_NOT_CONNECTED) { + DEBUG_WEBSOCKETS("[WS-Client] connection lost.\n"); + // do cleanup + clientDisconnect(client); + } + } + + if(client->tcp) { + // do cleanup + clientDisconnect(client); + } + + return false; +} +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * Handel incomming data from Client + */ +void WebSocketsClient::handleClientData(void) { + if((_client.status == WSC_HEADER || _client.status == WSC_BODY) && _lastHeaderSent + WEBSOCKETS_TCP_TIMEOUT < millis()) { + DEBUG_WEBSOCKETS("[WS-Client][handleClientData] header response timeout.. disconnecting!\n"); + clientDisconnect(&_client); + WEBSOCKETS_YIELD(); + return; + } + + int len = _client.tcp->available(); + if(len > 0) { + switch(_client.status) { + case WSC_HEADER: { + String headerLine = _client.tcp->readStringUntil('\n'); + handleHeader(&_client, &headerLine); + } break; + case WSC_BODY: { + char buf[256] = { 0 }; + _client.tcp->readBytes(&buf[0], std::min((size_t)len, sizeof(buf))); + String bodyLine = buf; + handleHeader(&_client, &bodyLine); + } break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(&_client); + break; + default: + WebSockets::clientDisconnect(&_client, 1002); + break; + } + } + WEBSOCKETS_YIELD(); +} +#endif + +/** + * send the WebSocket header to Server + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::sendHeader(WSclient_t * client) { + static const char * NEW_LINE = "\r\n"; + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header...\n"); + + uint8_t randomKey[16] = { 0 }; + + for(uint8_t i = 0; i < sizeof(randomKey); i++) { + randomKey[i] = random(0xFF); + } + + client->cKey = base64_encode(&randomKey[0], 16); + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + String handshake; + bool ws_header = true; + String url = client->cUrl; + + if(client->isSocketIO) { + if(client->cSessionId.length() == 0) { + url += WEBSOCKETS_STRING("&transport=polling"); + ws_header = false; + } else { + url += WEBSOCKETS_STRING("&transport=websocket&sid="); + url += client->cSessionId; + } + } + + handshake = WEBSOCKETS_STRING("GET "); + handshake += url + WEBSOCKETS_STRING( + " HTTP/1.1\r\n" + "Host: "); + handshake += _host + ":" + _port + NEW_LINE; + + if(ws_header) { + handshake += WEBSOCKETS_STRING( + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: "); + handshake += client->cKey + NEW_LINE; + + if(client->cProtocol.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: "); + handshake += client->cProtocol + NEW_LINE; + } + + if(client->cExtensions.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Extensions: "); + handshake += client->cExtensions + NEW_LINE; + } + } else { + handshake += WEBSOCKETS_STRING("Connection: keep-alive\r\n"); + } + + // add extra headers; by default this includes "Origin: file://" + if(client->extraHeaders.length() > 0) { + handshake += client->extraHeaders + NEW_LINE; + } + + handshake += WEBSOCKETS_STRING("User-Agent: arduino-WebSocket-Client\r\n"); + + if(client->base64Authorization.length() > 0) { + handshake += WEBSOCKETS_STRING("Authorization: Basic "); + handshake += client->base64Authorization + NEW_LINE; + } + + if(client->plainAuthorization.length() > 0) { + handshake += WEBSOCKETS_STRING("Authorization: "); + handshake += client->plainAuthorization + NEW_LINE; + } + + handshake += NEW_LINE; + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] handshake %s", (uint8_t *)handshake.c_str()); + write(client, (uint8_t *)handshake.c_str(), handshake.length()); + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); +#endif + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header... Done (%luus).\n", (micros() - start)); + _lastHeaderSent = millis(); +} + +/** + * handle the WebSocket header reading + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) { + headerLine->trim(); // remove \r + + // this code handels the http body for Socket.IO V3 requests + if(headerLine->length() > 0 && client->isSocketIO && client->status == WSC_BODY && client->cSessionId.length() == 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] socket.io json: %s\n", headerLine->c_str()); + String sid_begin = WEBSOCKETS_STRING("\"sid\":\""); + if(headerLine->indexOf(sid_begin) > -1) { + int start = headerLine->indexOf(sid_begin) + sid_begin.length(); + int end = headerLine->indexOf('"', start); + client->cSessionId = headerLine->substring(start, end); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cSessionId: %s\n", client->cSessionId.c_str()); + + // Trigger websocket connection code path + *headerLine = ""; + } + } + + // headle HTTP header + if(headerLine->length() > 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n", headerLine->c_str()); + + if(headerLine->startsWith(WEBSOCKETS_STRING("HTTP/1."))) { + // "HTTP/1.1 101 Switching Protocols" + client->cCode = headerLine->substring(9, headerLine->indexOf(' ', 9)).toInt(); + } else if(headerLine->indexOf(':') >= 0) { + String headerName = headerLine->substring(0, headerLine->indexOf(':')); + String headerValue = headerLine->substring(headerLine->indexOf(':') + 1); + + // remove space in the beginning (RFC2616) + if(headerValue[0] == ' ') { + headerValue.remove(0, 1); + } + + if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Connection"))) { + if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("upgrade"))) { + client->cIsUpgrade = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Upgrade"))) { + if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("websocket"))) { + client->cIsWebsocket = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Accept"))) { + client->cAccept = headerValue; + client->cAccept.trim(); // see rfc6455 + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) { + client->cProtocol = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) { + client->cExtensions = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Version"))) { + client->cVersion = headerValue.toInt(); + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Set-Cookie"))) { + if(headerValue.indexOf(';') > -1) { + client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1, headerValue.indexOf(";")); + } else { + client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1); + } + } + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str()); + } + + (*headerLine) = ""; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); +#endif + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Client settings:\n"); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cURL: %s\n", client->cUrl.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n", client->cKey.c_str()); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Server header:\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cCode: %d\n", client->cCode); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsUpgrade: %d\n", client->cIsUpgrade); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsWebsocket: %d\n", client->cIsWebsocket); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cAccept: %s\n", client->cAccept.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cProtocol: %s\n", client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cExtensions: %s\n", client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cVersion: %d\n", client->cVersion); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cSessionId: %s\n", client->cSessionId.c_str()); + + if(client->isSocketIO && client->cSessionId.length() == 0 && clientIsConnected(client)) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still missing cSessionId try socket.io V3\n"); + client->status = WSC_BODY; + return; + } else { + client->status = WSC_HEADER; + } + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + + if(ok) { + switch(client->cCode) { + case 101: ///< Switching Protocols + + break; + case 200: + if(client->isSocketIO) { + break; + } + // falls through + case 403: ///< Forbidden + // todo handle login + // falls through + default: ///< Server dont unterstand requrst + ok = false; + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] serverCode is not 101 (%d)\n", client->cCode); + clientDisconnect(client); + _lastConnectionFail = millis(); + break; + } + } + + if(ok) { + if(client->cAccept.length() == 0) { + ok = false; + } else { + // generate Sec-WebSocket-Accept key for check + String sKey = acceptKey(client->cKey); + if(sKey != client->cAccept) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\n"); + ok = false; + } + } + } + + if(ok) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection init done.\n"); + headerDone(client); + + runCbEvent(WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length()); +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + } else if(client->isSocketIO) { + if(client->cSessionId.length() > 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] found cSessionId\n"); + if(clientIsConnected(client) && _client.tcp->available()) { + // read not needed data + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still data in buffer (%d), clean up.\n", _client.tcp->available()); + while(_client.tcp->available() > 0) { + _client.tcp->read(); + } + } + sendHeader(client); + } +#endif + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n"); + _lastConnectionFail = millis(); + if(clientIsConnected(client)) { + write(client, "This is a webSocket client!"); + } + clientDisconnect(client); + } + } +} + +void WebSocketsClient::connectedCb() { + DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u.\n", _host.c_str(), _port); + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + _client.tcp->onDisconnect(std::bind([](WebSocketsClient * c, AsyncTCPbuffer * obj, WSclient_t * client) -> bool { + DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num); + client->status = WSC_NOT_CONNECTED; + client->tcp = NULL; + + // reconnect + c->asyncConnect(); + + return true; + }, + this, std::placeholders::_1, &_client)); +#endif + + _client.status = WSC_HEADER; + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + // set Timeout for readBytesUntil and readStringUntil + _client.tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); +#endif + +#if !defined(LIBRETINY) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + _client.tcp->setNoDelay(true); +#endif +#endif + +#if defined(HAS_SSL) +#if defined(SSL_AXTLS) || defined(ESP32) + if(_client.isSSL && SSL_FINGERPRINT_IS_SET) { + if(!_client.ssl->verify(_fingerprint.c_str(), _host.c_str())) { + DEBUG_WEBSOCKETS("[WS-Client] certificate mismatch\n"); + WebSockets::clientDisconnect(&_client, 1000); + return; + } +#else + if(_client.isSSL && SSL_FINGERPRINT_IS_SET) { +#endif + } else if(_client.isSSL && !_CA_cert) { +#if defined(SSL_BARESSL) + _client.ssl->setInsecure(); +#endif + } +#endif + + // send Header to Server + sendHeader(&_client); +} + +void WebSocketsClient::connectFailedCb() { + DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Failed\n", _host.c_str(), _port); +} + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + +void WebSocketsClient::asyncConnect() { + DEBUG_WEBSOCKETS("[WS-Client] asyncConnect...\n"); + + AsyncClient * tcpclient = new AsyncClient(); + + if(!tcpclient) { + DEBUG_WEBSOCKETS("[WS-Client] creating AsyncClient class failed!\n"); + return; + } + + tcpclient->onDisconnect([](void * obj, AsyncClient * c) { + c->free(); + delete c; + }); + + tcpclient->onConnect(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) { + ws->_client.tcp = new AsyncTCPbuffer(tcp); + if(!ws->_client.tcp) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!\n"); + ws->connectFailedCb(); + return; + } + ws->connectedCb(); + }, + this, std::placeholders::_2)); + + tcpclient->onError(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) { + ws->connectFailedCb(); + + // reconnect + ws->asyncConnect(); + }, + this, std::placeholders::_2)); + + if(!tcpclient->connect(_host.c_str(), _port)) { + connectFailedCb(); + delete tcpclient; + } +} + +#endif + +/** + * send heartbeat ping to server in set intervals + */ +void WebSocketsClient::handleHBPing() { + if(_client.pingInterval == 0) + return; + uint32_t pi = millis() - _client.lastPing; + if(pi > _client.pingInterval) { + DEBUG_WEBSOCKETS("[WS-Client] sending HB ping\n"); + if(sendPing()) { + _client.lastPing = millis(); + _client.pongReceived = false; + } else { + DEBUG_WEBSOCKETS("[WS-Client] sending HB ping failed\n"); + WebSockets::clientDisconnect(&_client, 1000); + } + } +} + +/** + * enable ping/pong heartbeat process + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSocketsClient::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { + WebSockets::enableHeartbeat(&_client, pingInterval, pongTimeout, disconnectTimeoutCount); +} + +/** + * disable ping/pong heartbeat process + */ +void WebSocketsClient::disableHeartbeat() { + _client.pingInterval = 0; +} diff --git a/lib/LT_WebSockets/src/WebSocketsClient.h b/lib/LT_WebSockets/src/WebSocketsClient.h new file mode 100644 index 00000000..6be0d777 --- /dev/null +++ b/lib/LT_WebSockets/src/WebSocketsClient.h @@ -0,0 +1,171 @@ +/** + * @file WebSocketsClient.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSCLIENT_H_ +#define WEBSOCKETSCLIENT_H_ + +#include "WebSockets.h" + +class WebSocketsClient : protected WebSockets { + public: +#ifdef __AVR__ + typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length); +#else + typedef std::function WebSocketClientEvent; +#endif + + WebSocketsClient(void); + virtual ~WebSocketsClient(void); + + void begin(const char * host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); + void begin(String host, uint16_t port, String url = "/", String protocol = "arduino"); +#ifndef LIBRETINY + void begin(IPAddress host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); +#endif + +#if defined(HAS_SSL) +#ifdef SSL_AXTLS + void beginSSL(const char * host, uint16_t port, const char * url = "/", const char * fingerprint = "", const char * protocol = "arduino"); + void beginSSL(String host, uint16_t port, String url = "/", String fingerprint = "", String protocol = "arduino"); +#else + void beginSSL(const char * host, uint16_t port, const char * url = "/", const uint8_t * fingerprint = NULL, const char * protocol = "arduino"); + void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", BearSSL::X509List * CA_cert = NULL, const char * protocol = "arduino"); + void setSSLClientCertKey(BearSSL::X509List * clientCert = NULL, BearSSL::PrivateKey * clientPrivateKey = NULL); + void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL); +#endif + void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", const char * CA_cert = NULL, const char * protocol = "arduino"); +#endif + + void beginSocketIO(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void beginSocketIO(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); + +#if defined(HAS_SSL) + void beginSocketIOSSL(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void beginSocketIOSSL(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); + + void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * CA_cert = NULL, const char * protocol = "arduino"); +#if defined(SSL_BARESSL) + void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", BearSSL::X509List * CA_cert = NULL, const char * protocol = "arduino"); +#endif +#endif + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void loop(void); +#else + // Async interface not need a loop call + void loop(void) __attribute__((deprecated)) {} +#endif + + void onEvent(WebSocketClientEvent cbEvent); + + bool sendTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(const uint8_t * payload, size_t length = 0); + bool sendTXT(char * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(const char * payload, size_t length = 0); + bool sendTXT(String & payload); + bool sendTXT(char payload); + + bool sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false); + bool sendBIN(const uint8_t * payload, size_t length); + + bool sendPing(uint8_t * payload = NULL, size_t length = 0); + bool sendPing(String & payload); + + void disconnect(void); + + void setAuthorization(const char * user, const char * password); + void setAuthorization(const char * auth); + + void setExtraHeaders(const char * extraHeaders = NULL); + + void setReconnectInterval(unsigned long time); + + void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); + void disableHeartbeat(); + + bool isConnected(void); + + protected: + String _host; + uint16_t _port; + +#if defined(HAS_SSL) +#ifdef SSL_AXTLS + String _fingerprint; + const char * _CA_cert; +#define SSL_FINGERPRINT_IS_SET (_fingerprint.length()) +#define SSL_FINGERPRINT_NULL "" +#else + const uint8_t * _fingerprint; + BearSSL::X509List * _CA_cert; + BearSSL::X509List * _client_cert; + BearSSL::PrivateKey * _client_key; +#define SSL_FINGERPRINT_IS_SET (_fingerprint != NULL) +#define SSL_FINGERPRINT_NULL NULL +#endif + +#endif + WSclient_t _client; + + WebSocketClientEvent _cbEvent; + + unsigned long _lastConnectionFail; + unsigned long _reconnectInterval; + unsigned long _lastHeaderSent; + + void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void handleClientData(void); +#endif + + void sendHeader(WSclient_t * client); + void handleHeader(WSclient_t * client, String * headerLine); + + void connectedCb(); + void connectFailedCb(); + + void handleHBPing(); // send ping in specified intervals + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + void asyncConnect(); +#endif + + /** + * called for sending a Event to the app + * @param type WStype_t + * @param payload uint8_t * + * @param length size_t + */ + virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(type, payload, length); + } + } +}; + +#endif /* WEBSOCKETSCLIENT_H_ */ diff --git a/lib/LT_WebSockets/src/WebSocketsServer.cpp b/lib/LT_WebSockets/src/WebSocketsServer.cpp new file mode 100644 index 00000000..a189a63e --- /dev/null +++ b/lib/LT_WebSockets/src/WebSocketsServer.cpp @@ -0,0 +1,966 @@ +/** + * @file WebSocketsServer.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include "WebSocketsServer.h" +#ifdef LIBRETINY +#include "Common.h" +#endif +WebSocketsServerCore::WebSocketsServerCore(const String & origin, const String & protocol) { + _origin = origin; + _protocol = protocol; + _runnning = false; + _pingInterval = 0; + _pongTimeout = 0; + _disconnectTimeoutCount = 0; + + _cbEvent = NULL; + + _httpHeaderValidationFunc = NULL; + _mandatoryHttpHeaders = NULL; + _mandatoryHttpHeaderCount = 0; +} + +WebSocketsServer::WebSocketsServer(uint16_t port, const String & origin, const String & protocol) + : WebSocketsServerCore(origin, protocol) { + _port = port; + + _server = new WEBSOCKETS_NETWORK_SERVER_CLASS(port); + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + _server->onClient([](void * s, AsyncClient * c) { + ((WebSocketsServerCore *)s)->newClient(new AsyncTCPbuffer(c)); + }, + this); +#endif +} + +WebSocketsServerCore::~WebSocketsServerCore() { + // disconnect all clients + close(); + + if(_mandatoryHttpHeaders) + delete[] _mandatoryHttpHeaders; + + _mandatoryHttpHeaderCount = 0; +} + +WebSocketsServer::~WebSocketsServer() { +} + +/** + * called to initialize the Websocket server + */ +void WebSocketsServerCore::begin(void) { + // adjust clients storage: + // _clients[i]'s constructor are already called, + // all its members are initialized to their default value, + // except the ones explicitly detailed in WSclient_t() constructor. + // Then we need to initialize some members to non-trivial values: + for(int i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + _clients[i].init(i, _pingInterval, _pongTimeout, _disconnectTimeoutCount); + } + +#ifdef ESP8266 + randomSeed(RANDOM_REG32); +#elif defined(ESP32) +#define DR_REG_RNG_BASE 0x3ff75144 + randomSeed(READ_PERI_REG(DR_REG_RNG_BASE)); +#else + // TODO find better seed + randomSeed(millis()); +#endif + + _runnning = true; + + DEBUG_WEBSOCKETS("[WS-Server] Websocket Version: " WEBSOCKETS_VERSION "\n"); +} + +void WebSocketsServerCore::close(void) { + _runnning = false; + disconnect(); + + // restore _clients[] to their initial state + // before next call to ::begin() + for(int i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + _clients[i] = WSclient_t(); + } +} + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsServerCore::onEvent(WebSocketServerEvent cbEvent) { + _cbEvent = cbEvent; +} + +/* + * Sets the custom http header validator function + * @param httpHeaderValidationFunc WebSocketServerHttpHeaderValFunc ///< pointer to the custom http header validation function + * @param mandatoryHttpHeaders[] const char* ///< the array of named http headers considered to be mandatory / must be present in order for websocket upgrade to succeed + * @param mandatoryHttpHeaderCount size_t ///< the number of items in the mandatoryHttpHeaders array + */ +void WebSocketsServerCore::onValidateHttpHeader( + WebSocketServerHttpHeaderValFunc validationFunc, + const char * mandatoryHttpHeaders[], + size_t mandatoryHttpHeaderCount) { + _httpHeaderValidationFunc = validationFunc; + + if(_mandatoryHttpHeaders) + delete[] _mandatoryHttpHeaders; + + _mandatoryHttpHeaderCount = mandatoryHttpHeaderCount; + _mandatoryHttpHeaders = new String[_mandatoryHttpHeaderCount]; + + for(size_t i = 0; i < _mandatoryHttpHeaderCount; i++) { + _mandatoryHttpHeaders[i] = mandatoryHttpHeaders[i]; + } +} + +/* + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServerCore::sendTXT(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + if(length == 0) { + length = strlen((const char *)payload); + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return sendFrame(client, WSop_text, payload, length, true, headerToPayload); + } + return false; +} + +bool WebSocketsServerCore::sendTXT(uint8_t num, const uint8_t * payload, size_t length) { + return sendTXT(num, (uint8_t *)payload, length); +} + +bool WebSocketsServerCore::sendTXT(uint8_t num, char * payload, size_t length, bool headerToPayload) { + return sendTXT(num, (uint8_t *)payload, length, headerToPayload); +} + +bool WebSocketsServerCore::sendTXT(uint8_t num, const char * payload, size_t length) { + return sendTXT(num, (uint8_t *)payload, length); +} + +bool WebSocketsServerCore::sendTXT(uint8_t num, String & payload) { + return sendTXT(num, (uint8_t *)payload.c_str(), payload.length()); +} + +/** + * send text data to client all + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServerCore::broadcastTXT(uint8_t * payload, size_t length, bool headerToPayload) { + WSclient_t * client; + bool ret = true; + if(length == 0) { + length = strlen((const char *)payload); + } + + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + if(!sendFrame(client, WSop_text, payload, length, true, headerToPayload)) { + ret = false; + } + } + WEBSOCKETS_YIELD(); + } + return ret; +} + +bool WebSocketsServerCore::broadcastTXT(const uint8_t * payload, size_t length) { + return broadcastTXT((uint8_t *)payload, length); +} + +bool WebSocketsServerCore::broadcastTXT(char * payload, size_t length, bool headerToPayload) { + return broadcastTXT((uint8_t *)payload, length, headerToPayload); +} + +bool WebSocketsServerCore::broadcastTXT(const char * payload, size_t length) { + return broadcastTXT((uint8_t *)payload, length); +} + +bool WebSocketsServerCore::broadcastTXT(String & payload) { + return broadcastTXT((uint8_t *)payload.c_str(), payload.length()); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServerCore::sendBIN(uint8_t num, uint8_t * payload, size_t length, bool fin, bool continuation, bool headerToPayload) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + if(continuation) { + return sendFrame(client, WSop_continuation, payload, length, fin, headerToPayload); + } else { + return sendFrame(client, WSop_binary, payload, length, fin, headerToPayload); + } + } + return false; +} + +bool WebSocketsServerCore::sendBIN(uint8_t num, const uint8_t * payload, size_t length) { + return sendBIN(num, (uint8_t *)payload, length); +} + +/** + * send binary data to client all + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsServerCore::broadcastBIN(uint8_t * payload, size_t length, bool fin, bool continuation, bool headerToPayload) { + WSclient_t * client; + bool ret = true; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + if(continuation) { + ret = sendFrame(client, WSop_continuation, payload, length, fin, headerToPayload); + } else { + ret = sendFrame(client, WSop_binary, payload, length, fin, headerToPayload); + } + } + WEBSOCKETS_YIELD(); + } + return ret; +} + +bool WebSocketsServerCore::broadcastBIN(const uint8_t * payload, size_t length) { + return broadcastBIN((uint8_t *)payload, length); +} + +/** + * sends a WS ping to Client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsServerCore::sendPing(uint8_t num, uint8_t * payload, size_t length) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return sendFrame(client, WSop_ping, payload, length); + } + return false; +} + +bool WebSocketsServerCore::sendPing(uint8_t num, String & payload) { + return sendPing(num, (uint8_t *)payload.c_str(), payload.length()); +} + +/** + * sends a WS ping to all Client + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsServerCore::broadcastPing(uint8_t * payload, size_t length) { + WSclient_t * client; + bool ret = true; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + if(!sendFrame(client, WSop_ping, payload, length)) { + ret = false; + } + } + WEBSOCKETS_YIELD(); + } + return ret; +} + +bool WebSocketsServerCore::broadcastPing(String & payload) { + return broadcastPing((uint8_t *)payload.c_str(), payload.length()); +} + +/** + * disconnect all clients + */ +void WebSocketsServerCore::disconnect(void) { + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + WebSockets::clientDisconnect(client, 1000); + } + } +} + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsServerCore::disconnect(uint8_t num) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return; + } + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + WebSockets::clientDisconnect(client, 1000); + } +} + +/* + * set the Authorization for the http request + * @param user const char * + * @param password const char * + */ +void WebSocketsServerCore::setAuthorization(const char * user, const char * password) { + if(user && password) { + String auth = user; + auth += ":"; + auth += password; + _base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length()); + } +} + +/** + * set the Authorizatio for the http request + * @param auth const char * base64 + */ +void WebSocketsServerCore::setAuthorization(const char * auth) { + if(auth) { + _base64Authorization = auth; + } +} + +/** + * count the connected clients (optional ping them) + * @param ping bool ping the connected clients + */ +int WebSocketsServerCore::connectedClients(bool ping) { + WSclient_t * client; + int count = 0; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(client->status == WSC_CONNECTED) { + if(ping != true || sendPing(i)) { + count++; + } + } + } + return count; +} + +/** + * see if one client is connected + * @param num uint8_t client id + */ +bool WebSocketsServerCore::clientIsConnected(uint8_t num) { + if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) { + return false; + } + WSclient_t * client = &_clients[num]; + return clientIsConnected(client); +} + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +/** + * get an IP for a client + * @param num uint8_t client id + * @return IPAddress + */ +IPAddress WebSocketsServerCore::remoteIP(uint8_t num) { + if(num < WEBSOCKETS_SERVER_CLIENT_MAX) { + WSclient_t * client = &_clients[num]; + if(clientIsConnected(client)) { + return client->tcp->remoteIP(); + } + } + + return IPAddress(); +} +#endif + +//################################################################################# +//################################################################################# +//################################################################################# + +/** + * handle new client connection + * @param client + */ +WSclient_t * WebSocketsServerCore::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient) { + WSclient_t * client; + // search free list entry for client + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + + // state is not connected or tcp connection is lost + if(!clientIsConnected(client)) { + client->tcp = TCPclient; +#if defined(HAS_SSL) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + client->isSSL = false; + client->tcp->setNoDelay(true); +#endif +#endif //ssl +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + // set Timeout for readBytesUntil and readStringUntil + client->tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); +#endif + client->status = WSC_HEADER; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#ifndef NODEBUG_WEBSOCKETS + IPAddress ip = client->tcp->remoteIP(); +#endif + DEBUG_WEBSOCKETS("[WS-Server][%d] new client from %d.%d.%d.%d\n", client->num, ip[0], ip[1], ip[2], ip[3]); +#else + DEBUG_WEBSOCKETS("[WS-Server][%d] new client\n", client->num); +#endif + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->onDisconnect(std::bind([](WebSocketsServerCore * server, AsyncTCPbuffer * obj, WSclient_t * client) -> bool { + DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num); + + AsyncTCPbuffer ** sl = &server->_clients[client->num].tcp; + if(*sl == obj) { + client->status = WSC_NOT_CONNECTED; + *sl = NULL; + } + return true; + }, + this, std::placeholders::_1, client)); + + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsServerCore::handleHeader, this, client, &(client->cHttpLine))); +#endif + + client->pingInterval = _pingInterval; + client->pongTimeout = _pongTimeout; + client->disconnectTimeoutCount = _disconnectTimeoutCount; + client->lastPing = millis(); + client->pongReceived = false; + + return client; + break; + } + } + return nullptr; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param length size_t + */ +void WebSocketsServerCore::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) { + WStype_t type = WStype_ERROR; + + switch(opcode) { + case WSop_text: + type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START; + break; + case WSop_binary: + type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START; + break; + case WSop_continuation: + type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT; + break; + case WSop_ping: + type = WStype_PING; + break; + case WSop_pong: + type = WStype_PONG; + break; + case WSop_close: + default: + break; + } + + runCbEvent(client->num, type, payload, length); +} + +/** + * Discard a native client + * @param client WSclient_t * ptr to the client struct contaning the native client "->tcp" + */ +void WebSocketsServerCore::dropNativeClient(WSclient_t * client) { + if(!client) { + return; + } + if(client->tcp) { + if(client->tcp->connected()) { +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) && (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP32) + client->tcp->flush(); +#endif + client->tcp->stop(); + } +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->status = WSC_NOT_CONNECTED; +#else + delete client->tcp; +#endif + client->tcp = NULL; + } +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsServerCore::clientDisconnect(WSclient_t * client) { +#if defined(HAS_SSL) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + if(client->isSSL && client->ssl) { + if(client->ssl->connected()) { + client->ssl->flush(); + client->ssl->stop(); + } + delete client->ssl; + client->ssl = NULL; + client->tcp = NULL; + } +#endif +#endif //ssl + + dropNativeClient(client); + + client->cUrl = ""; + client->cKey = ""; + client->cProtocol = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + + client->cWsRXsize = 0; + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->cHttpLine = ""; +#endif + + client->status = WSC_NOT_CONNECTED; + + DEBUG_WEBSOCKETS("[WS-Server][%d] client disconnected.\n", client->num); + + runCbEvent(client->num, WStype_DISCONNECTED, NULL, 0); +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = connected + */ +bool WebSocketsServerCore::clientIsConnected(WSclient_t * client) { + if(!client->tcp) { + return false; + } + + if(client->tcp->connected()) { + if(client->status != WSC_NOT_CONNECTED) { + return true; + } + } else { + // client lost + if(client->status != WSC_NOT_CONNECTED) { + DEBUG_WEBSOCKETS("[WS-Server][%d] client connection lost.\n", client->num); + // do cleanup + clientDisconnect(client); + } + } + + if(client->tcp) { + // do cleanup + DEBUG_WEBSOCKETS("[WS-Server][%d] client list cleanup.\n", client->num); + clientDisconnect(client); + } + + return false; +} +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * Handle incoming Connection Request + */ +WSclient_t * WebSocketsServerCore::handleNewClient(WEBSOCKETS_NETWORK_CLASS * tcpClient) { + WSclient_t * client = newClient(tcpClient); + + if(!client) { + // no free space to handle client +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#ifndef NODEBUG_WEBSOCKETS + IPAddress ip = tcpClient->remoteIP(); +#endif + DEBUG_WEBSOCKETS("[WS-Server] no free space new client from %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); +#else + DEBUG_WEBSOCKETS("[WS-Server] no free space new client\n"); +#endif + // no client! => create dummy! + WSclient_t dummy = WSclient_t(); + client = &dummy; + client->tcp = tcpClient; + dropNativeClient(client); + } + + WEBSOCKETS_YIELD(); + + return client; +} + +/** + * Handle incoming Connection Request + */ +void WebSocketsServer::handleNewClients(void) { +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + while(_server->hasClient()) { +#endif + + // store new connection + WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->available()); + if(!tcpClient) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); + return; + } + + handleNewClient(tcpClient); + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + } +#endif +} + +/** + * Handel incomming data from Client + */ +void WebSocketsServerCore::handleClientData(void) { + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + if(clientIsConnected(client)) { + int len = client->tcp->available(); + if(len > 0) { + // DEBUG_WEBSOCKETS("[WS-Server][%d][handleClientData] len: %d\n", client->num, len); + switch(client->status) { + case WSC_HEADER: { + String headerLine = client->tcp->readStringUntil('\n'); + handleHeader(client, &headerLine); + } break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(client); + break; + default: + DEBUG_WEBSOCKETS("[WS-Server][%d][handleClientData] unknown client status %d\n", client->num, client->status); + WebSockets::clientDisconnect(client, 1002); + break; + } + } + + handleHBPing(client); + handleHBTimeout(client); + } + WEBSOCKETS_YIELD(); + } +} +#endif + +/* + * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection + * @param headerName String ///< the name of the header being checked + */ +bool WebSocketsServerCore::hasMandatoryHeader(String headerName) { + for(size_t i = 0; i < _mandatoryHttpHeaderCount; i++) { + if(_mandatoryHttpHeaders[i].equalsIgnoreCase(headerName)) + return true; + } + return false; +} + +/** + * handles http header reading for WebSocket upgrade + * @param client WSclient_t * ///< pointer to the client struct + * @param headerLine String ///< the header being read / processed + */ +void WebSocketsServerCore::handleHeader(WSclient_t * client, String * headerLine) { + static const char * NEW_LINE = "\r\n"; + + headerLine->trim(); // remove \r + + if(headerLine->length() > 0) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] RX: %s\n", client->num, headerLine->c_str()); + + // websocket requests always start with GET see rfc6455 + if(headerLine->startsWith("GET ")) { + // cut URL out + client->cUrl = headerLine->substring(4, headerLine->indexOf(' ', 4)); + + // reset non-websocket http header validation state for this client + client->cHttpHeadersValid = true; + client->cMandatoryHeadersCount = 0; + + } else if(headerLine->indexOf(':') >= 0) { + String headerName = headerLine->substring(0, headerLine->indexOf(':')); + String headerValue = headerLine->substring(headerLine->indexOf(':') + 1); + + // remove space in the beginning (RFC2616) + if(headerValue[0] == ' ') { + headerValue.remove(0, 1); + } + + if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Connection"))) { + headerValue.toLowerCase(); + if(headerValue.indexOf(WEBSOCKETS_STRING("upgrade")) >= 0) { + client->cIsUpgrade = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Upgrade"))) { + if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("websocket"))) { + client->cIsWebsocket = true; + } + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Version"))) { + client->cVersion = headerValue.toInt(); + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Key"))) { + client->cKey = headerValue; + client->cKey.trim(); // see rfc6455 + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) { + client->cProtocol = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) { + client->cExtensions = headerValue; + } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Authorization"))) { + client->base64Authorization = headerValue; + } else { + client->cHttpHeadersValid &= execHttpHeaderValidation(headerName, headerValue); + if(_mandatoryHttpHeaderCount > 0 && hasMandatoryHeader(headerName)) { + client->cMandatoryHeadersCount++; + } + } + + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str()); + } + + (*headerLine) = ""; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsServerCore::handleHeader, this, client, &(client->cHttpLine))); +#endif + } else { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] Header read fin.\n", client->num); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cURL: %s\n", client->num, client->cUrl.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cIsUpgrade: %d\n", client->num, client->cIsUpgrade); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cIsWebsocket: %d\n", client->num, client->cIsWebsocket); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cKey: %s\n", client->num, client->cKey.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cProtocol: %s\n", client->num, client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cExtensions: %s\n", client->num, client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cVersion: %d\n", client->num, client->cVersion); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - base64Authorization: %s\n", client->num, client->base64Authorization.c_str()); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cHttpHeadersValid: %d\n", client->num, client->cHttpHeadersValid); + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - cMandatoryHeadersCount: %d\n", client->num, client->cMandatoryHeadersCount); + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + + if(ok) { + if(client->cUrl.length() == 0) { + ok = false; + } + if(client->cKey.length() == 0) { + ok = false; + } + if(client->cVersion != 13) { + ok = false; + } + if(!client->cHttpHeadersValid) { + ok = false; + } + if(client->cMandatoryHeadersCount != _mandatoryHttpHeaderCount) { + ok = false; + } + } + + if(_base64Authorization.length() > 0) { + String auth = WEBSOCKETS_STRING("Basic "); + auth += _base64Authorization; + if(auth != client->base64Authorization) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] HTTP Authorization failed!\n", client->num); + handleAuthorizationFailed(client); + return; + } + } + + if(ok) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] Websocket connection incoming.\n", client->num); + + // generate Sec-WebSocket-Accept key + String sKey = acceptKey(client->cKey); + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] - sKey: %s\n", client->num, sKey.c_str()); + + client->status = WSC_CONNECTED; + + String handshake = WEBSOCKETS_STRING( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: arduino-WebSocketsServer\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Accept: "); + handshake += sKey + NEW_LINE; + + if(_origin.length() > 0) { + handshake += WEBSOCKETS_STRING("Access-Control-Allow-Origin: "); + handshake += _origin + NEW_LINE; + } + + if(client->cProtocol.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: "); + handshake += _protocol + NEW_LINE; + } + + // header end + handshake += NEW_LINE; + + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] handshake %s", client->num, (uint8_t *)handshake.c_str()); + + write(client, (uint8_t *)handshake.c_str(), handshake.length()); + + headerDone(client); + + // send ping + WebSockets::sendFrame(client, WSop_ping); + + runCbEvent(client->num, WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length()); + + } else { + handleNonWebsocketConnection(client); + } + } +} + +/** + * send heartbeat ping to server in set intervals + */ +void WebSocketsServerCore::handleHBPing(WSclient_t * client) { + if(client->pingInterval == 0) + return; + uint32_t pi = millis() - client->lastPing; + if(pi > client->pingInterval) { + DEBUG_WEBSOCKETS("[WS-Server][%d] sending HB ping\n", client->num); + if(sendPing(client->num)) { + client->lastPing = millis(); + client->pongReceived = false; + } + } +} + +/** + * enable ping/pong heartbeat process + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSocketsServerCore::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { + _pingInterval = pingInterval; + _pongTimeout = pongTimeout; + _disconnectTimeoutCount = disconnectTimeoutCount; + + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + WebSockets::enableHeartbeat(client, pingInterval, pongTimeout, disconnectTimeoutCount); + } +} + +/** + * disable ping/pong heartbeat process + */ +void WebSocketsServerCore::disableHeartbeat() { + _pingInterval = 0; + + WSclient_t * client; + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { + client = &_clients[i]; + client->pingInterval = 0; + } +} + +//////////////////// +// WebSocketServer + +/** + * called to initialize the Websocket server + */ +void WebSocketsServer::begin(void) { + WebSocketsServerCore::begin(); + _server->begin(); + + DEBUG_WEBSOCKETS("[WS-Server] Server Started.\n"); +} + +void WebSocketsServer::close(void) { + WebSocketsServerCore::close(); +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + _server->close(); +#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + _server->end(); +#else + // TODO how to close server? +#endif +} + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * called in arduino loop + */ +void WebSocketsServerCore::loop(void) { + if(_runnning) { + WEBSOCKETS_YIELD(); + handleClientData(); + } +} + +/** + * called in arduino loop + */ +void WebSocketsServer::loop(void) { + if(_runnning) { + WEBSOCKETS_YIELD(); + handleNewClients(); + WebSocketsServerCore::loop(); + } +} +#endif diff --git a/lib/LT_WebSockets/src/WebSocketsServer.h b/lib/LT_WebSockets/src/WebSocketsServer.h new file mode 100644 index 00000000..a8472a5b --- /dev/null +++ b/lib/LT_WebSockets/src/WebSocketsServer.h @@ -0,0 +1,244 @@ +/** + * @file WebSocketsServer.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSSERVER_H_ +#define WEBSOCKETSSERVER_H_ + +#include "WebSockets.h" + +#ifndef WEBSOCKETS_SERVER_CLIENT_MAX +#define WEBSOCKETS_SERVER_CLIENT_MAX (5) +#endif + +class WebSocketsServerCore : protected WebSockets { + public: + WebSocketsServerCore(const String & origin = "", const String & protocol = "arduino"); + virtual ~WebSocketsServerCore(void); + + void begin(void); + void close(void); + +#ifdef __AVR__ + typedef void (*WebSocketServerEvent)(uint8_t num, WStype_t type, uint8_t * payload, size_t length); + typedef bool (*WebSocketServerHttpHeaderValFunc)(String headerName, String headerValue); +#else + typedef std::function WebSocketServerEvent; + typedef std::function WebSocketServerHttpHeaderValFunc; +#endif + + void onEvent(WebSocketServerEvent cbEvent); + void onValidateHttpHeader( + WebSocketServerHttpHeaderValFunc validationFunc, + const char * mandatoryHttpHeaders[], + size_t mandatoryHttpHeaderCount); + + bool sendTXT(uint8_t num, uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(uint8_t num, const uint8_t * payload, size_t length = 0); + bool sendTXT(uint8_t num, char * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(uint8_t num, const char * payload, size_t length = 0); + bool sendTXT(uint8_t num, String & payload); + + bool broadcastTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool broadcastTXT(const uint8_t * payload, size_t length = 0); + bool broadcastTXT(char * payload, size_t length = 0, bool headerToPayload = false); + bool broadcastTXT(const char * payload, size_t length = 0); + bool broadcastTXT(String & payload); + + bool sendBIN(uint8_t num, uint8_t * payload, size_t length, bool fin = true, bool continuation = false, bool headerToPayload = false); + bool sendBIN(uint8_t num, const uint8_t * payload, size_t length); + + bool broadcastBIN(uint8_t * payload, size_t length, bool fin = true, bool continuation = false, bool headerToPayload = false); + bool broadcastBIN(const uint8_t * payload, size_t length); + + bool sendPing(uint8_t num, uint8_t * payload = NULL, size_t length = 0); + bool sendPing(uint8_t num, String & payload); + + bool broadcastPing(uint8_t * payload = NULL, size_t length = 0); + bool broadcastPing(String & payload); + + void disconnect(void); + void disconnect(uint8_t num); + + void setAuthorization(const char * user, const char * password); + void setAuthorization(const char * auth); + + int connectedClients(bool ping = false); + + bool clientIsConnected(uint8_t num); + + void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); + void disableHeartbeat(); + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + IPAddress remoteIP(uint8_t num); +#endif + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void loop(void); // handle client data only +#endif + + WSclient_t * newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient); + + protected: + String _origin; + String _protocol; + String _base64Authorization; ///< Base64 encoded Auth request + String * _mandatoryHttpHeaders; + size_t _mandatoryHttpHeaderCount; + + WSclient_t _clients[WEBSOCKETS_SERVER_CLIENT_MAX]; + + WebSocketServerEvent _cbEvent; + WebSocketServerHttpHeaderValFunc _httpHeaderValidationFunc; + + bool _runnning; + + uint32_t _pingInterval; + uint32_t _pongTimeout; + uint8_t _disconnectTimeoutCount; + + void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void handleClientData(void); +#endif + + void handleHeader(WSclient_t * client, String * headerLine); + + void handleHBPing(WSclient_t * client); // send ping in specified intervals + + /** + * called if a non Websocket connection is coming in. + * Note: can be override + * @param client WSclient_t * ptr to the client struct + */ + virtual void handleNonWebsocketConnection(WSclient_t * client) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] no Websocket connection close.\n", client->num); + client->tcp->write( + "HTTP/1.1 400 Bad Request\r\n" + "Server: arduino-WebSocket-Server\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 32\r\n" + "Connection: close\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + "This is a Websocket server only!"); + clientDisconnect(client); + } + + /** + * called if a non Authorization connection is coming in. + * Note: can be override + * @param client WSclient_t * ptr to the client struct + */ + virtual void handleAuthorizationFailed(WSclient_t * client) { + client->tcp->write( + "HTTP/1.1 401 Unauthorized\r\n" + "Server: arduino-WebSocket-Server\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 45\r\n" + "Connection: close\r\n" + "Sec-WebSocket-Version: 13\r\n" + "WWW-Authenticate: Basic realm=\"WebSocket Server\"" + "\r\n" + "This Websocket server requires Authorization!"); + clientDisconnect(client); + } + + /** + * called for sending a Event to the app + * @param num uint8_t + * @param type WStype_t + * @param payload uint8_t * + * @param length size_t + */ + virtual void runCbEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(num, type, payload, length); + } + } + + /* + * Called at client socket connect handshake negotiation time for each http header that is not + * a websocket specific http header (not Connection, Upgrade, Sec-WebSocket-*) + * If the custom httpHeaderValidationFunc returns false for any headerName / headerValue passed, the + * socket negotiation is considered invalid and the upgrade to websockets request is denied / rejected + * This mechanism can be used to enable custom authentication schemes e.g. test the value + * of a session cookie to determine if a user is logged on / authenticated + */ + virtual bool execHttpHeaderValidation(String headerName, String headerValue) { + if(_httpHeaderValidationFunc) { + // return the value of the custom http header validation function + return _httpHeaderValidationFunc(headerName, headerValue); + } + // no custom http header validation so just assume all is good + return true; + } + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + WSclient_t * handleNewClient(WEBSOCKETS_NETWORK_CLASS * tcpClient); +#endif + + /** + * drop native tcp connection (client->tcp) + */ + void dropNativeClient(WSclient_t * client); + + private: + /* + * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection + * @param headerName String ///< the name of the header being checked + */ + bool hasMandatoryHeader(String headerName); +}; + +class WebSocketsServer : public WebSocketsServerCore { + public: + WebSocketsServer(uint16_t port, const String & origin = "", const String & protocol = "arduino"); + virtual ~WebSocketsServer(void); + + void begin(void); + void close(void); + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void loop(void); // handle incoming client and client data +#else + // Async interface not need a loop call + void loop(void) __attribute__((deprecated)) { + } +#endif + + protected: +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void handleNewClients(void); +#endif + + uint16_t _port; + WEBSOCKETS_NETWORK_SERVER_CLASS * _server; +}; + +#endif /* WEBSOCKETSSERVER_H_ */ diff --git a/lib/LT_WebSockets/src/WebSocketsVersion.h b/lib/LT_WebSockets/src/WebSocketsVersion.h new file mode 100644 index 00000000..bf2526c1 --- /dev/null +++ b/lib/LT_WebSockets/src/WebSocketsVersion.h @@ -0,0 +1,36 @@ +/** + * @file WebSocketsVersion.h + * @date 05.04.2022 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSVERSION_H_ +#define WEBSOCKETSVERSION_H_ + +#define WEBSOCKETS_VERSION "2.3.7" + +#define WEBSOCKETS_VERSION_MAJOR 2 +#define WEBSOCKETS_VERSION_MINOR 3 +#define WEBSOCKETS_VERSION_PATCH 7 + +#define WEBSOCKETS_VERSION_INT 2003007 + +#endif /* WEBSOCKETSVERSION_H_ */ diff --git a/lib/LT_WebSockets/travis/common.sh b/lib/LT_WebSockets/travis/common.sh new file mode 100644 index 00000000..c1c23285 --- /dev/null +++ b/lib/LT_WebSockets/travis/common.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +set -ex + +function build_sketches() +{ + local arduino=$1 + local srcpath=$2 + local platform=$3 + local sketches=$(find $srcpath -name *.ino) + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + if [[ -f "$sketchdir/.$platform.skip" ]]; then + echo -e "\n\n ------------ Skipping $sketch ------------ \n\n"; + continue + fi + echo -e "\n\n ------------ Building $sketch ------------ \n\n"; + $arduino --verify $sketch; + local result=$? + if [ $result -ne 0 ]; then + echo "Build failed ($sketch) build verbose..." + $arduino --verify --verbose --preserve-temp-files $sketch + result=$? + fi + if [ $result -ne 0 ]; then + echo "Build failed ($1) $sketch" + return $result + fi + done +} + +function build_sketch() +{ + local arduino=$1 + local sketch=$2 + $arduino --verify $sketch; + local result=$? + if [ $result -ne 0 ]; then + echo "Build failed ($sketch) build verbose..." + $arduino --verify --verbose --preserve-temp-files $sketch + result=$? + fi + if [ $result -ne 0 ]; then + echo "Build failed ($1) $sketch" + return $result + fi +} + +function get_sketches_json() +{ + local arduino=$1 + local srcpath=$2 + local platform=$3 + local sketches=($(find $srcpath -name *.ino)) + echo -en "[" + for sketch in "${sketches[@]}" ; do + local sketchdir=$(dirname $sketch) + if [[ -f "$sketchdir/.$platform.skip" ]]; then + continue + fi + echo -en "\"$sketch\"" + if [[ $sketch != ${sketches[-1]} ]] ; then + echo -en "," + fi + + done + echo -en "]" +} + +function get_sketches_json_matrix() +{ + local arduino=$1 + local srcpath=$2 + local platform=$3 + local ideversion=$4 + local board=$5 + local sketches=($(find $srcpath -name *.ino)) + for sketch in "${sketches[@]}" ; do + local sketchdir=$(dirname $sketch) + local sketchname=$(basename $sketch) + if [[ -f "$sketchdir/.$platform.skip" ]]; then + continue + fi + echo -en "{\"name\":\"$sketchname\",\"board\":\"$board\",\"ideversion\":\"$ideversion\",\"cpu\":\"$platform\",\"sketch\":\"$sketch\"}" + if [[ $sketch != ${sketches[-1]} ]] ; then + echo -en "," + fi + done +} + +function get_core() +{ + echo Setup core for $1 + + cd $HOME/arduino_ide/hardware + + if [ "$1" = "esp8266" ] ; then + mkdir esp8266com + cd esp8266com + git clone --depth 1 https://github.com/esp8266/Arduino.git esp8266 + cd esp8266/ + git submodule update --init + rm -rf .git + cd tools + python get.py + fi + + if [ "$1" = "esp32" ] ; then + mkdir espressif + cd espressif + git clone --depth 1 https://github.com/espressif/arduino-esp32.git esp32 + cd esp32/ + rm -rf .git + cd tools + python get.py + fi + +} + +function clone_library() { + local url=$1 + echo clone $(basename $url) + mkdir -p $HOME/Arduino/libraries + cd $HOME/Arduino/libraries + git clone --depth 1 $url + rm -rf */.git + rm -rf */.github + rm -rf */examples +} + +function hash_library_names() { + cd $HOME/Arduino/libraries + ls | sha1sum -z | cut -c1-5 +} \ No newline at end of file diff --git a/lib/LT_WebSockets/travis/version.py b/lib/LT_WebSockets/travis/version.py new file mode 100644 index 00000000..71454abb --- /dev/null +++ b/lib/LT_WebSockets/travis/version.py @@ -0,0 +1,132 @@ +#!/usr/bin/python3 + +import json +import configparser +import argparse +import re +import os +import datetime + +travis_dir = os.path.dirname(os.path.abspath(__file__)) +base_dir = os.path.abspath(travis_dir + "/../") + +def write_header_file(version): + hvs = version.split('.') + intversion = int(hvs[0]) * 1000000 + int(hvs[1]) * 1000 + int(hvs[2]) + now = datetime.datetime.now() + + text = f'''/** + * @file WebSocketsVersion.h + * @date {now.strftime("%d.%m.%Y")} + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WEBSOCKETSVERSION_H_ +#define WEBSOCKETSVERSION_H_ + +#define WEBSOCKETS_VERSION "{version}" + +#define WEBSOCKETS_VERSION_MAJOR {hvs[0]} +#define WEBSOCKETS_VERSION_MINOR {hvs[1]} +#define WEBSOCKETS_VERSION_PATCH {hvs[2]} + +#define WEBSOCKETS_VERSION_INT {intversion} + +#endif /* WEBSOCKETSVERSION_H_ */ +''' + with open(f'{base_dir}/src/WebSocketsVersion.h', 'w') as f: + f.write(text) + + +def get_library_properties_version(): + library_properties = {} + with open(f'{base_dir}/library.properties', 'r') as f: + library_properties = configparser.ConfigParser() + library_properties.read_string('[root]\n' + f.read()) + return library_properties['root']['version'] + + +def get_library_json_version(): + library_json = {} + with open(f'{base_dir}/library.json', 'r') as f: + library_json = json.load(f) + return library_json['version'] + + +def get_header_versions(): + data = {} + define = re.compile('^#define WEBSOCKETS_VERSION_?(.*) "?([0-9\.]*)"?$') + with open(f'{base_dir}/src/WebSocketsVersion.h', 'r') as f: + for line in f: + m = define.match(line) + if m: + name = m[1] + if name == "": + name = "VERSION" + data[name] = m[2] + return data + + +parser = argparse.ArgumentParser(description='Checks and update Version files') +parser.add_argument( + '--update', action='store_true', default=False) +parser.add_argument( + '--check', action='store_true', default=True) + +args = parser.parse_args() + +if args.update: + library_properties_version = get_library_properties_version() + + with open(f'{base_dir}/library.json', 'r') as f: + library_json = json.load(f) + + library_json['version'] = library_properties_version + + with open(f'{base_dir}/library.json', 'w') as f: + json.dump(library_json, f, indent=4, sort_keys=True) + + write_header_file(library_properties_version) + + +library_json_version = get_library_json_version() +library_properties_version = get_library_properties_version() +header_version = get_header_versions() + +print("WebSocketsVersion.h", header_version) +print(f"library.json: {library_json_version}") +print(f"library.properties: {library_properties_version}") + +if args.check: + if library_json_version != library_properties_version or header_version['VERSION'] != library_properties_version: + raise Exception('versions did not match!') + + hvs = header_version['VERSION'].split('.') + if header_version['MAJOR'] != hvs[0]: + raise Exception('header MAJOR version wrong!') + if header_version['MINOR'] != hvs[1]: + raise Exception('header MINOR version wrong!') + if header_version['PATCH'] != hvs[2]: + raise Exception('header PATCH version wrong!') + + intversion = int(hvs[0]) * 1000000 + int(hvs[1]) * 1000 + int(hvs[2]) + if int(header_version['INT']) != intversion: + raise Exception('header INT version wrong!') diff --git a/lib/OneWire/OneWire.cpp b/lib/OneWire/OneWire.cpp new file mode 100644 index 00000000..5312f51b --- /dev/null +++ b/lib/OneWire/OneWire.cpp @@ -0,0 +1,604 @@ +/* +Copyright (c) 2007, Jim Studt (original old version - many contributors since) + +The latest version of this library may be found at: + http://www.pjrc.com/teensy/td_libs_OneWire.html + +OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since +January 2010. + +DO NOT EMAIL for technical support, especially not for ESP chips! +All project support questions must be posted on public forums +relevant to the board or chips used. If using Arduino, post on +Arduino's forum. If using ESP, post on the ESP community forums. +There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL! + +Github's issue tracker for OneWire should be used only to report +specific bugs. DO NOT request project support via Github. All +project and tech support questions must be posted on forums, not +github issues. If you experience a problem and you are not +absolutely sure it's an issue with the library, ask on a forum +first. Only use github to report issues after experts have +confirmed the issue is with OneWire rather than your project. + +Back in 2010, OneWire was in need of many bug fixes, but had +been abandoned the original author (Jim Studt). None of the known +contributors were interested in maintaining OneWire. Paul typically +works on OneWire every 6 to 12 months. Patches usually wait that +long. If anyone is interested in more actively maintaining OneWire, +please contact Paul (this is pretty much the only reason to use +private email about OneWire). + +OneWire is now very mature code. No changes other than adding +definitions for newer hardware support are anticipated. + + ESP32 mods authored by stickbreaker: + @stickbreaker 30APR2018 add IRAM_ATTR to read_bit() write_bit() to solve ICache miss timing failure. + thanks @everslick re: https://github.com/espressif/arduino-esp32/issues/1335 + Altered by garyd9 for clean merge with Paul Stoffregen's source + +Version 2.3: + Unknown chip fallback mode, Roger Clark + Teensy-LC compatibility, Paul Stoffregen + Search bug fix, Love Nystrom + +Version 2.2: + Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com + Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030 + Fix DS18B20 example negative temperature + Fix DS18B20 example's low res modes, Ken Butcher + Improve reset timing, Mark Tillotson + Add const qualifiers, Bertrik Sikken + Add initial value input to crc16, Bertrik Sikken + Add target_search() function, Scott Roberts + +Version 2.1: + Arduino 1.0 compatibility, Paul Stoffregen + Improve temperature example, Paul Stoffregen + DS250x_PROM example, Guillermo Lovato + PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com + Improvements from Glenn Trewitt: + - crc16() now works + - check_crc16() does all of calculation/checking work. + - Added read_bytes() and write_bytes(), to reduce tedious loops. + - Added ds2408 example. + Delete very old, out-of-date readme file (info is here) + +Version 2.0: Modifications by Paul Stoffregen, January 2010: +http://www.pjrc.com/teensy/td_libs_OneWire.html + Search fix from Robin James + http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + Use direct optimized I/O in all cases + Disable interrupts during timing critical sections + (this solves many random communication errors) + Disable interrupts during read-modify-write I/O + Reduce RAM consumption by eliminating unnecessary + variables and trimming many to 8 bits + Optimize both crc8 - table version moved to flash + +Modified to work with larger numbers of devices - avoids loop. +Tested in Arduino 11 alpha with 12 sensors. +26 Sept 2008 -- Robin James +http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + +Updated to work with arduino-0008 and to include skip() as of +2007/07/06. --RJL20 + +Modified to calculate the 8-bit CRC directly, avoiding the need for +the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 +-- Tom Pollard, Jan 23, 2008 + +Jim Studt's original library was modified by Josh Larios. + +Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008 + +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. + +Much of the code was inspired by Derek Yerger's code, though I don't +think much of that remains. In any event that was.. + (copyleft) 2006 by Derek Yerger - Free to distribute freely. + +The CRC code was excerpted and inspired by the Dallas Semiconductor +sample code bearing this copyright. +//--------------------------------------------------------------------------- +// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. +// +// 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 DALLAS SEMICONDUCTOR 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. +// +// Except as contained in this notice, the name of Dallas Semiconductor +// shall not be used except as stated in the Dallas Semiconductor +// Branding Policy. +//-------------------------------------------------------------------------- +*/ + +#include +#include "OneWire.h" +#include "util/OneWire_direct_gpio.h" + +#ifdef ARDUINO_ARCH_ESP32 +// due to the dual core esp32, a critical section works better than disabling interrupts +# define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux) +# define interrupts() portEXIT_CRITICAL(&mux);} +// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html +# define CRIT_TIMING IRAM_ATTR +#else +# define CRIT_TIMING +#endif + + +void OneWire::begin(uint8_t pin) +{ + pinMode(pin, INPUT); + bitmask = PIN_TO_BITMASK(pin); + baseReg = PIN_TO_BASEREG(pin); +#if ONEWIRE_SEARCH + reset_search(); +#endif +} + + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return a 0; +// +// Returns 1 if a device asserted a presence pulse, 0 otherwise. +// +uint8_t CRIT_TIMING OneWire::reset(void) +{ + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + __attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; + uint8_t r; + uint8_t retries = 125; + + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); + interrupts(); + // wait until the wire is high... just in case + do { + if (--retries == 0) return 0; + delayMicroseconds(2); + } while ( !DIRECT_READ(reg, mask)); + + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + interrupts(); + delayMicroseconds(480); + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); // allow it to float + delayMicroseconds(70); + r = !DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(410); + return r; +} + +// +// Write a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +void CRIT_TIMING OneWire::write_bit(uint8_t v) +{ + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + __attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; + + if (v & 1) { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(10); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(55); + } else { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(65); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(5); + } +} + +// +// Read a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +uint8_t CRIT_TIMING OneWire::read_bit(void) +{ + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + __attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; + uint8_t r; + + noInterrupts(); + DIRECT_MODE_OUTPUT(reg, mask); + DIRECT_WRITE_LOW(reg, mask); + delayMicroseconds(3); + DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise + delayMicroseconds(10); + r = DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(53); + return r; +} + +// +// Write a byte. The writing code uses the active drivers to raise the +// pin high, if you need power after the write (e.g. DS18S20 in +// parasite power mode) then set 'power' to 1, otherwise the pin will +// go tri-state at the end of the write to avoid heating in a short or +// other mishap. +// +void OneWire::write(uint8_t v, uint8_t power /* = 0 */) { + uint8_t bitMask; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + OneWire::write_bit( (bitMask & v)?1:0); + } + if ( !power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) { + for (uint16_t i = 0 ; i < count ; i++) + write(buf[i]); + if (!power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +// +// Read a byte +// +uint8_t OneWire::read() { + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if ( OneWire::read_bit()) r |= bitMask; + } + return r; +} + +void OneWire::read_bytes(uint8_t *buf, uint16_t count) { + for (uint16_t i = 0 ; i < count ; i++) + buf[i] = read(); +} + +// +// Do a ROM select +// +void OneWire::select(const uint8_t rom[8]) +{ + uint8_t i; + + write(0x55); // Choose ROM + + for (i = 0; i < 8; i++) write(rom[i]); +} + +// +// Do a ROM skip +// +void OneWire::skip() +{ + write(0xCC); // Skip ROM +} + +void OneWire::depower() +{ + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + interrupts(); +} + +#if ONEWIRE_SEARCH + +// +// You need to use this function to start a search again from the beginning. +// You do not need to do it for the first search, though you could. +// +void OneWire::reset_search() +{ + // reset the search state + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + for(int i = 7; ; i--) { + ROM_NO[i] = 0; + if ( i == 0) break; + } +} + +// Setup the search to find the device type 'family_code' on the next call +// to search(*newAddr) if it is present. +// +void OneWire::target_search(uint8_t family_code) +{ + // set the search state to find SearchFamily type devices + ROM_NO[0] = family_code; + for (uint8_t i = 1; i < 8; i++) + ROM_NO[i] = 0; + LastDiscrepancy = 64; + LastFamilyDiscrepancy = 0; + LastDeviceFlag = false; +} + +// +// Perform a search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWire::address variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return TRUE : device found, ROM number in ROM_NO buffer +// FALSE : device not found, end of search +// +bool OneWire::search(uint8_t *newAddr, bool search_mode /* = true */) +{ + uint8_t id_bit_number; + uint8_t last_zero, rom_byte_number; + bool search_result; + uint8_t id_bit, cmp_id_bit; + + unsigned char rom_byte_mask, search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = false; + + // if the last call was not the last one + if (!LastDeviceFlag) { + // 1-Wire reset + if (!reset()) { + // reset the search + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + return false; + } + + // issue the search command + if (search_mode == true) { + write(0xF0); // NORMAL SEARCH + } else { + write(0xEC); // CONDITIONAL SEARCH + } + + // loop to do the search + do + { + // read a bit and its complement + id_bit = read_bit(); + cmp_id_bit = read_bit(); + + // check for no devices on 1-wire + if ((id_bit == 1) && (cmp_id_bit == 1)) { + break; + } else { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) { + search_direction = id_bit; // bit write value for search + } else { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < LastDiscrepancy) { + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + } else { + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + } + // if 0 was picked then record its position in LastZero + if (search_direction == 0) { + last_zero = id_bit_number; + + // check for Last discrepancy in family + if (last_zero < 9) + LastFamilyDiscrepancy = last_zero; + } + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction == 1) + ROM_NO[rom_byte_number] |= rom_byte_mask; + else + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + write_bit(search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } + while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) { + // search successful so set LastDiscrepancy,LastDeviceFlag,search_result + LastDiscrepancy = last_zero; + + // check for last device + if (LastDiscrepancy == 0) { + LastDeviceFlag = true; + } + search_result = true; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !ROM_NO[0]) { + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + search_result = false; + } else { + for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; + } + return search_result; + } + +#endif + +#if ONEWIRE_CRC +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#if ONEWIRE_CRC8_TABLE +// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0 +// Tiny 2x16 entry CRC table created by Arjen Lentz +// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table +static const uint8_t PROGMEM dscrc2x16_table[] = { + 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, + 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, + 0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8, + 0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74 +}; + +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (Use tiny 2x16 entry CRC table) +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + crc = *addr++ ^ crc; // just re-using crc as intermediate + crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^ + pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f)); + } + + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but a little smaller, than the lookup table. +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { +#if defined(__AVR__) + crc = _crc_ibutton_update(crc, *addr++); +#else + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } +#endif + } + return crc; +} +#endif + +#if ONEWIRE_CRC16 +bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc) +{ + crc = ~crc16(input, len, crc); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc) +{ +#if defined(__AVR__) + for (uint16_t i = 0 ; i < len ; i++) { + crc = _crc16_update(crc, input[i]); + } +#else + static const uint8_t oddparity[16] = + { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + for (uint16_t i = 0 ; i < len ; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } +#endif + return crc; +} +#endif + +#endif + +// The ESP32 Arduino core may provide noInterrupts/interrupts as macros. +// Ensure the original definitions are restored after including this file. +#ifdef ARDUINO_ARCH_ESP32 +# undef noInterrupts +# undef interrupts +#endif +// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html +#undef CRIT_TIMING diff --git a/lib/OneWire/OneWire.h b/lib/OneWire/OneWire.h new file mode 100644 index 00000000..80c98f94 --- /dev/null +++ b/lib/OneWire/OneWire.h @@ -0,0 +1,183 @@ +#ifndef OneWire_h +#define OneWire_h + +#ifdef __cplusplus + +#include + +#if defined(__AVR__) +#include +#endif + +#if ARDUINO >= 100 +#include // for delayMicroseconds, digitalPinToBitMask, etc +#else +#include "WProgram.h" // for delayMicroseconds +#include "pins_arduino.h" // for digitalPinToBitMask, etc +#endif + +// You can exclude certain features from OneWire. In theory, this +// might save some space. In practice, the compiler automatically +// removes unused code (technically, the linker, using -fdata-sections +// and -ffunction-sections when compiling, and Wl,--gc-sections +// when linking), so most of these will not result in any code size +// reduction. Well, unless you try to use the missing features +// and redesign your program to not need them! ONEWIRE_CRC8_TABLE +// is the exception, because it selects a fast but large algorithm +// or a small but slow algorithm. + +// you can exclude onewire_search by defining that to 0 +#ifndef ONEWIRE_SEARCH +#define ONEWIRE_SEARCH 1 +#endif + +// You can exclude CRC checks altogether by defining this to 0 +#ifndef ONEWIRE_CRC +#define ONEWIRE_CRC 1 +#endif + +// Select the table-lookup method of computing the 8-bit CRC +// by setting this to 1. The lookup table enlarges code size by +// about 250 bytes. It does NOT consume RAM (but did in very +// old versions of OneWire). If you disable this, a slower +// but very compact algorithm is used. +#ifndef ONEWIRE_CRC8_TABLE +#define ONEWIRE_CRC8_TABLE 1 +#endif + +// You can allow 16-bit CRC checks by defining this to 1 +// (Note that ONEWIRE_CRC must also be 1.) +#ifndef ONEWIRE_CRC16 +#define ONEWIRE_CRC16 1 +#endif + +// Board-specific macros for direct GPIO +#include "util/OneWire_direct_regtype.h" + +class OneWire +{ + private: + IO_REG_TYPE bitmask; + volatile IO_REG_TYPE *baseReg; + +#if ONEWIRE_SEARCH + // global search state + unsigned char ROM_NO[8]; + uint8_t LastDiscrepancy; + uint8_t LastFamilyDiscrepancy; + bool LastDeviceFlag; +#endif + + public: + OneWire() { } + OneWire(uint8_t pin) { begin(pin); } + void begin(uint8_t pin); + + // Perform a 1-Wire reset cycle. Returns 1 if a device responds + // with a presence pulse. Returns 0 if there is no device or the + // bus is shorted or otherwise held low for more than 250uS + uint8_t reset(void); + + // Issue a 1-Wire rom select command, you do the reset first. + void select(const uint8_t rom[8]); + + // Issue a 1-Wire rom skip command, to address all on bus. + void skip(void); + + // Write a byte. If 'power' is one then the wire is held high at + // the end for parasitically powered devices. You are responsible + // for eventually depowering it by calling depower() or doing + // another read or write. + void write(uint8_t v, uint8_t power = 0); + + void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0); + + // Read a byte. + uint8_t read(void); + + void read_bytes(uint8_t *buf, uint16_t count); + + // Write a bit. The bus is always left powered at the end, see + // note in write() about that. + void write_bit(uint8_t v); + + // Read a bit. + uint8_t read_bit(void); + + // Stop forcing power onto the bus. You only need to do this if + // you used the 'power' flag to write() or used a write_bit() call + // and aren't about to do another read or write. You would rather + // not leave this powered if you don't have to, just in case + // someone shorts your bus. + void depower(void); + +#if ONEWIRE_SEARCH + // Clear the search state so that if will start from the beginning again. + void reset_search(); + + // Setup the search to find the device type 'family_code' on the next call + // to search(*newAddr) if it is present. + void target_search(uint8_t family_code); + + // Look for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are + // no devices, or you have already retrieved all of them. It + // might be a good idea to check the CRC to make sure you didn't + // get garbage. The order is deterministic. You will always get + // the same devices in the same order. + bool search(uint8_t *newAddr, bool search_mode = true); +#endif + +#if ONEWIRE_CRC + // Compute a Dallas Semiconductor 8 bit CRC, these are used in the + // ROM and scratchpad registers. + static uint8_t crc8(const uint8_t *addr, uint8_t len); + +#if ONEWIRE_CRC16 + // Compute the 1-Wire CRC16 and compare it against the received CRC. + // Example usage (reading a DS2408): + // // Put everything in a buffer so we can compute the CRC easily. + // uint8_t buf[13]; + // buf[0] = 0xF0; // Read PIO Registers + // buf[1] = 0x88; // LSB address + // buf[2] = 0x00; // MSB address + // WriteBytes(net, buf, 3); // Write 3 cmd bytes + // ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + // if (!CheckCRC16(buf, 11, &buf[11])) { + // // Handle error. + // } + // + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param inverted_crc - The two CRC16 bytes in the received data. + // This should just point into the received data, + // *not* at a 16-bit integer. + // @param crc - The crc starting value (optional) + // @return True, iff the CRC matches. + static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0); + + // Compute a Dallas Semiconductor 16 bit CRC. This is required to check + // the integrity of data received from many 1-Wire devices. Note that the + // CRC computed here is *not* what you'll get from the 1-Wire network, + // for two reasons: + // 1) The CRC is transmitted bitwise inverted. + // 2) Depending on the endian-ness of your processor, the binary + // representation of the two-byte return value may have a different + // byte order than the two bytes you get from 1-Wire. + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param crc - The crc starting value (optional) + // @return The CRC16, as defined by Dallas Semiconductor. + static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0); +#endif +#endif +}; + +// Prevent this name from leaking into Arduino sketches +#ifdef IO_REG_TYPE +#undef IO_REG_TYPE +#endif + +#endif // __cplusplus +#endif // OneWire_h + diff --git a/lib/OneWire/keywords.txt b/lib/OneWire/keywords.txt new file mode 100644 index 00000000..8336bc33 --- /dev/null +++ b/lib/OneWire/keywords.txt @@ -0,0 +1,39 @@ +####################################### +# Syntax Coloring Map For OneWire +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +OneWire KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +reset KEYWORD2 +write_bit KEYWORD2 +read_bit KEYWORD2 +write KEYWORD2 +write_bytes KEYWORD2 +read KEYWORD2 +read_bytes KEYWORD2 +select KEYWORD2 +skip KEYWORD2 +depower KEYWORD2 +reset_search KEYWORD2 +search KEYWORD2 +crc8 KEYWORD2 +crc16 KEYWORD2 +check_crc16 KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/lib/OneWire/library.json b/lib/OneWire/library.json new file mode 100644 index 00000000..c540133b --- /dev/null +++ b/lib/OneWire/library.json @@ -0,0 +1,61 @@ +{ + "name": "OneWire", + "description": "Control 1-Wire protocol (DS18S20, DS18B20, DS2408 and etc)", + "keywords": "onewire, 1-wire, bus, sensor, temperature, ibutton", + "authors": [ + { + "name": "Paul Stoffregen", + "email": "paul@pjrc.com", + "url": "http://www.pjrc.com", + "maintainer": true + }, + { + "name": "Jim Studt" + }, + { + "name": "Tom Pollard", + "email": "pollard@alum.mit.edu" + }, + { + "name": "Derek Yerger" + }, + { + "name": "Josh Larios" + }, + { + "name": "Robin James" + }, + { + "name": "Glenn Trewitt" + }, + { + "name": "Jason Dangel", + "email": "dangel.jason AT gmail.com" + }, + { + "name": "Guillermo Lovato" + }, + { + "name": "Ken Butcher" + }, + { + "name": "Mark Tillotson" + }, + { + "name": "Bertrik Sikken" + }, + { + "name": "Scott Roberts" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/PaulStoffregen/OneWire" + }, + "version": "2.3.8", + "homepage": "https://www.pjrc.com/teensy/td_libs_OneWire.html", + "frameworks": "Arduino", + "examples": [ + "examples/*/*.pde" + ] +} diff --git a/lib/OneWire/library.properties b/lib/OneWire/library.properties new file mode 100644 index 00000000..f4c596f9 --- /dev/null +++ b/lib/OneWire/library.properties @@ -0,0 +1,10 @@ +name=OneWire +version=2.3.8 +author=Jim Studt, Tom Pollard, Robin James, Glenn Trewitt, Jason Dangel, Guillermo Lovato, Paul Stoffregen, Scott Roberts, Bertrik Sikken, Mark Tillotson, Ken Butcher, Roger Clark, Love Nystrom +maintainer=Paul Stoffregen +sentence=Access 1-wire temperature sensors, memory and other chips. +paragraph= +category=Communication +url=http://www.pjrc.com/teensy/td_libs_OneWire.html +architectures=* + diff --git a/lib/OneWire/util/OneWire_direct_gpio.h b/lib/OneWire/util/OneWire_direct_gpio.h new file mode 100644 index 00000000..f0d047a3 --- /dev/null +++ b/lib/OneWire/util/OneWire_direct_gpio.h @@ -0,0 +1,519 @@ +#ifndef OneWire_Direct_GPIO_h +#define OneWire_Direct_GPIO_h + +// This header should ONLY be included by OneWire.cpp. These defines are +// meant to be private, used within OneWire.cpp, but not exposed to Arduino +// sketches or other libraries which may include OneWire.h. + +#include + +// Platform specific I/O definitions + +#if defined(__AVR__) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_BASE_ATTR asm("r30") +#define IO_REG_MASK_ATTR +#if defined(__AVR_ATmega4809__) +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask)) +#else +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) +#endif + +#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (1) +#define IO_REG_TYPE uint8_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR __attribute__ ((unused)) +#define DIRECT_READ(base, mask) (*((base)+512)) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) + +#elif defined(__MKL26Z64__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) + +#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask)) + +#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) +// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. +// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 +// If you have trouble with OneWire on Arduino Due, please check the +// status of delayMicroseconds() before reporting a bug in OneWire! +#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) +#ifndef PROGMEM +#define PROGMEM +#endif +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const uint8_t *)(addr)) +#endif + +#elif defined(__PIC32MX__) +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 + +#elif defined(ARDUINO_ARCH_ESP8266) +// Special note: I depend on the ESP community to maintain these definitions and +// submit good pull requests. I can not answer any ESP questions or help you +// resolve any problems related to ESP chips. Please do not contact me and please +// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked +// on ESP community forums. +#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) +#define PIN_TO_BITMASK(pin) (1UL << (pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE mask) +{ + if(mask > 0x8000) + { + GP16FFS(GPFFS_GPIO(16)); + GPC16 = 0; + GP16E &= ~1; + } + else + { + GPE &= ~(mask); + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE mask) +{ + if(mask > 0x8000) + { + GP16FFS(GPFFS_GPIO(16)); + GPC16 = 0; + GP16E |= 1; + } + else + { + GPE |= (mask); + } +} +static inline __attribute__((always_inline)) +bool directRead(IO_REG_TYPE mask) +{ + if(mask > 0x8000) + return GP16I & 0x01; + else + return ((GPI & (mask)) ? true : false); +} + +#define DIRECT_READ(base, mask) directRead(mask) +#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) +#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) +#define DIRECT_WRITE_LOW(base, mask) (mask > 0x8000) ? GP16O &= ~1 : (GPOC = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (mask > 0x8000) ? GP16O |= 1 : (GPOS = (mask)) + +#elif defined(ARDUINO_ARCH_ESP32) +#include +#include +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + return (GPIO.in.val >> pin) & 0x1; +#else // plain ESP32 + if ( pin < 32 ) + return (GPIO.in >> pin) & 0x1; + else if ( pin < 46 ) + return (GPIO.in1.val >> (pin - 32)) & 0x1; +#endif + + return 0; +} + +static inline __attribute__((always_inline)) +void directWriteLow(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.out_w1tc.val = ((uint32_t)1 << pin); +#else // plain ESP32 + if ( pin < 32 ) + GPIO.out_w1tc = ((uint32_t)1 << pin); + else if ( pin < 46 ) + GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); +#endif +} + +static inline __attribute__((always_inline)) +void directWriteHigh(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.out_w1ts.val = ((uint32_t)1 << pin); +#else // plain ESP32 + if ( pin < 32 ) + GPIO.out_w1ts = ((uint32_t)1 << pin); + else if ( pin < 46 ) + GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); +#endif +} + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.enable_w1tc.val = ((uint32_t)1 << (pin)); +#else + if ( digitalPinIsValid(pin) ) + { +#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4 + uint32_t rtc_reg(rtc_gpio_desc[pin].reg); + + if ( rtc_reg ) // RTC pins PULL settings + { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } +#endif + // Input + if ( pin < 32 ) + GPIO.enable_w1tc = ((uint32_t)1 << pin); + else + GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32)); + } +#endif +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.enable_w1ts.val = ((uint32_t)1 << (pin)); +#else + if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs + { +#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4 + uint32_t rtc_reg(rtc_gpio_desc[pin].reg); + + if ( rtc_reg ) // RTC pins PULL settings + { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } +#endif + // Output + if ( pin < 32 ) + GPIO.enable_w1ts = ((uint32_t)1 << pin); + else // already validated to pins <= 33 + GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32)); + } +#endif +} + +#define DIRECT_READ(base, pin) directRead(pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin) +// https://github.com/PaulStoffregen/OneWire/pull/47 +// https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c +#ifdef interrupts +#undef interrupts +#endif +#ifdef noInterrupts +#undef noInterrupts +#endif +#define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux) +#define interrupts() portEXIT_CRITICAL(&mux);} +//#warning "ESP32 OneWire testing" + +#elif defined(ARDUINO_ARCH_STM32) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) digitalReadFast((PinName)pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0)) +#define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0)) + +#elif defined(__SAMD21G18A__) +#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) + +#elif defined(__ASR6501__) +#define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT) +#define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT) +#define PORT_OFFSET(port) (PORT_REG_SHFIT * port) +#define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin))) + +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin)) +#define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin)) +#define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin)) +#define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ) +#define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG) + +#elif defined(RBL_NRF51822) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) +#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) +#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) +#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) +#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) + +#elif defined(__arc__) /* Arduino101/Genuino101 specifics */ + +#include "scss_registers.h" +#include "portable.h" +#include "avr/pgmspace.h" + +#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) +#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) +#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) +#define DIR_OFFSET_SS 0x01 +#define DIR_OFFSET_SOC 0x04 +#define EXT_PORT_OFFSET_SS 0x0A +#define EXT_PORT_OFFSET_SOC 0x50 + +/* GPIO registers base address */ +#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) +#define PIN_TO_BITMASK(pin) pin +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + IO_REG_TYPE ret; + if (SS_GPIO == GPIO_TYPE(pin)) { + ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); + } else { + ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC); + } + return ((ret >> GPIO_ID(pin)) & 0x01); +} + +static inline __attribute__((always_inline)) +void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin)); + } +} + +#define DIRECT_READ(base, pin) directRead(base, pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) + +#elif defined(__riscv) + +/* + * Tested on highfive1 + * + * Stable results are achieved operating in the + * two high speed modes of the highfive1. It + * seems to be less reliable in slow mode. + */ +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(IO_REG_TYPE mask) +{ + return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0; +} + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; + + GPIO_REG(GPIO_INPUT_EN) |= mask; + GPIO_REG(GPIO_OUTPUT_EN) &= ~mask; +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; + + GPIO_REG(GPIO_INPUT_EN) &= ~mask; + GPIO_REG(GPIO_OUTPUT_EN) |= mask; +} + +static inline __attribute__((always_inline)) +void directWriteLow(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask; +} + +static inline __attribute__((always_inline)) +void directWriteHigh(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_VAL) |= mask; +} + +#define DIRECT_READ(base, mask) directRead(mask) +#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask) +#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask) +#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) +#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) + +#elif defined(__MBED__) + +#include "platform/mbed_critical.h" +#include "DigitalInOut.h" +#include +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (new mbed::DigitalInOut(digitalPinToPinName(pin))) +#define IO_REG_TYPE mbed::DigitalInOut* +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) (*pin) +#define DIRECT_WRITE_LOW(base, pin) (*pin = 0) +#define DIRECT_WRITE_HIGH(base, pin) (*pin = 1) +#define DIRECT_MODE_INPUT(base, pin) (pin->input()) +#define DIRECT_MODE_OUTPUT(base, pin) (pin->output()) +#undef interrupts +#undef noInterrupts +#define noInterrupts() osThreadSetPriority(osThreadGetId(), osPriorityRealtime) //core_util_critical_section_enter() +#define interrupts() osThreadSetPriority(osThreadGetId(), osPriorityNormal) //core_util_critical_section_exit() + +#elif defined(ARDUINO_ARCH_MBED_RP2040)|| defined(ARDUINO_ARCH_RP2040) +#define delayMicroseconds(time) busy_wait_us(time) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE unsigned int +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) digitalRead(pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) +#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) +#warning "OneWire. RP2040 in Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite." + +#else +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE unsigned int +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) digitalRead(pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) +#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) +#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture." + +#endif + +#endif diff --git a/lib/OneWire/util/OneWire_direct_regtype.h b/lib/OneWire/util/OneWire_direct_regtype.h new file mode 100644 index 00000000..8e30813a --- /dev/null +++ b/lib/OneWire/util/OneWire_direct_regtype.h @@ -0,0 +1,59 @@ +#ifndef OneWire_Direct_RegType_h +#define OneWire_Direct_RegType_h + +#include + +// Platform specific I/O register type + +#if defined(__AVR__) +#define IO_REG_TYPE uint8_t + +#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) +#define IO_REG_TYPE uint8_t + +#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) +#define IO_REG_TYPE uint32_t + +#elif defined(__MKL26Z64__) +#define IO_REG_TYPE uint8_t + +#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) +#define IO_REG_TYPE uint32_t + +#elif defined(__PIC32MX__) +#define IO_REG_TYPE uint32_t + +#elif defined(ARDUINO_ARCH_ESP8266) +#define IO_REG_TYPE uint32_t + +#elif defined(ARDUINO_ARCH_ESP32) +#define IO_REG_TYPE uint32_t +#define IO_REG_MASK_ATTR + +#elif defined(ARDUINO_ARCH_STM32) +#define IO_REG_TYPE uint32_t + +#elif defined(__SAMD21G18A__) +#define IO_REG_TYPE uint32_t + +#elif defined(__ASR6501__) +#define IO_REG_TYPE uint32_t + +#elif defined(RBL_NRF51822) +#define IO_REG_TYPE uint32_t + +#elif defined(__arc__) /* Arduino101/Genuino101 specifics */ +#define IO_REG_TYPE uint32_t + +#elif defined(__MBED__) +#include "DigitalInOut.h" +#define IO_REG_TYPE mbed::DigitalInOut* + +#elif defined(__riscv) +#define IO_REG_TYPE uint32_t + +#else +#define IO_REG_TYPE unsigned int + +#endif +#endif diff --git a/lib/TickerScheduler/TickerScheduler.cpp b/lib/TickerScheduler/TickerScheduler.cpp index af47ffa1..6821422a 100644 --- a/lib/TickerScheduler/TickerScheduler.cpp +++ b/lib/TickerScheduler/TickerScheduler.cpp @@ -105,7 +105,7 @@ void TickerScheduler::update() { if (this->items[i].is_used) { - #ifdef ARDUINO_ARCH_AVR + #if defined ARDUINO_ARCH_AVR || defined LIBRETINY this->items[i].t.Tick(); #endif diff --git a/lib/TickerScheduler/TickerScheduler.h b/lib/TickerScheduler/TickerScheduler.h index 98ae1f05..ec2e82da 100644 --- a/lib/TickerScheduler/TickerScheduler.h +++ b/lib/TickerScheduler/TickerScheduler.h @@ -7,7 +7,7 @@ #include -#ifdef ARDUINO_ARCH_AVR +#if defined ARDUINO_ARCH_AVR || defined LIBRETINY class Ticker { typedef void(*ticker_callback_t)(bool*); @@ -41,12 +41,17 @@ class Ticker this->is_attached = true; } }; -#endif + +#else //#ifdef ARDUINO_ARCH_ESP8266 #include #include -//#endif +#endif +#if defined LIBRETINY +#include +#endif + void tickerFlagHandle(volatile bool * flag); diff --git a/lib/WebSockets/src/WebSocketsServer.cpp b/lib/WebSockets/src/WebSocketsServer.cpp index 39d8d3e2..60a038cb 100644 --- a/lib/WebSockets/src/WebSocketsServer.cpp +++ b/lib/WebSockets/src/WebSocketsServer.cpp @@ -25,6 +25,14 @@ #include "WebSockets.h" #include "WebSocketsServer.h" +#ifdef ESP32 +#if defined __has_include +#if __has_include("soc/wdev_reg.h") +#include "soc/wdev_reg.h" +#endif // __has_include +#endif // defined __has_include +#endif + WebSocketsServerCore::WebSocketsServerCore(const String & origin, const String & protocol) { _origin = origin; _protocol = protocol; @@ -65,6 +73,7 @@ WebSocketsServerCore::~WebSocketsServerCore() { } WebSocketsServer::~WebSocketsServer() { + delete _server; } /** @@ -82,9 +91,13 @@ void WebSocketsServerCore::begin(void) { #ifdef ESP8266 randomSeed(RANDOM_REG32); +#elif defined(ESP32) && defined(WDEV_RND_REG) + randomSeed(REG_READ(WDEV_RND_REG)); #elif defined(ESP32) #define DR_REG_RNG_BASE 0x3ff75144 randomSeed(READ_PERI_REG(DR_REG_RNG_BASE)); +#elif defined(ARDUINO_ARCH_RP2040) + randomSeed(rp2040.hwrand32()); #else // TODO find better seed randomSeed(millis()); @@ -400,7 +413,7 @@ bool WebSocketsServerCore::clientIsConnected(uint8_t num) { return clientIsConnected(client); } -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) /** * get an IP for a client * @param num uint8_t client id @@ -432,8 +445,16 @@ WSclient_t * WebSocketsServerCore::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclien for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { client = &_clients[i]; - // state is not connected or tcp connection is lost - if(!clientIsConnected(client)) { + // look for match to existing socket before creating a new one + if(clientIsConnected(client)) { +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_W5100) + // Check to see if it is the same socket - if so, return it + if(client->tcp->getSocketNumber() == TCPclient->getSocketNumber()) { + return client; + } +#endif + } else { + // state is not connected or tcp connection is lost client->tcp = TCPclient; #if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) @@ -445,7 +466,7 @@ WSclient_t * WebSocketsServerCore::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclien client->tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); #endif client->status = WSC_HEADER; -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) #ifndef NODEBUG_WEBSOCKETS IPAddress ip = client->tcp->remoteIP(); #endif @@ -527,7 +548,7 @@ void WebSocketsServerCore::dropNativeClient(WSclient_t * client) { } if(client->tcp) { if(client->tcp->connected()) { -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) && (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) && (WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP32) && (WEBSOCKETS_NETWORK_TYPE != NETWORK_RP2040) client->tcp->flush(); #endif client->tcp->stop(); @@ -546,7 +567,7 @@ void WebSocketsServerCore::dropNativeClient(WSclient_t * client) { * @param client WSclient_t * ptr to the client struct */ void WebSocketsServerCore::clientDisconnect(WSclient_t * client) { -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) if(client->isSSL && client->ssl) { if(client->ssl->connected()) { client->ssl->flush(); @@ -620,7 +641,7 @@ WSclient_t * WebSocketsServerCore::handleNewClient(WEBSOCKETS_NETWORK_CLASS * tc if(!client) { // no free space to handle client -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) #ifndef NODEBUG_WEBSOCKETS IPAddress ip = tcpClient->remoteIP(); #endif @@ -633,6 +654,7 @@ WSclient_t * WebSocketsServerCore::handleNewClient(WEBSOCKETS_NETWORK_CLASS * tc client = &dummy; client->tcp = tcpClient; dropNativeClient(client); + return nullptr; } WEBSOCKETS_YIELD(); @@ -644,12 +666,16 @@ WSclient_t * WebSocketsServerCore::handleNewClient(WEBSOCKETS_NETWORK_CLASS * tc * Handle incoming Connection Request */ void WebSocketsServer::handleNewClients(void) { -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) while(_server->hasClient()) { #endif - // store new connection - WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->available()); +// store new connection +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) + WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->available()); //available +#else + WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->accept()); //available +#endif if(!tcpClient) { DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); return; @@ -657,7 +683,7 @@ void WebSocketsServer::handleNewClients(void) { handleNewClient(tcpClient); -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) } #endif } @@ -928,7 +954,7 @@ void WebSocketsServer::begin(void) { void WebSocketsServer::close(void) { WebSocketsServerCore::close(); -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) _server->close(); #elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) _server->end(); diff --git a/lib/WebSockets/src/WebSocketsServer.h b/lib/WebSockets/src/WebSocketsServer.h index a8472a5b..5000f577 100644 --- a/lib/WebSockets/src/WebSocketsServer.h +++ b/lib/WebSockets/src/WebSocketsServer.h @@ -90,7 +90,7 @@ class WebSocketsServerCore : protected WebSockets { void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); void disableHeartbeat(); -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) IPAddress remoteIP(uint8_t num); #endif @@ -241,4 +241,4 @@ class WebSocketsServer : public WebSocketsServerCore { WEBSOCKETS_NETWORK_SERVER_CLASS * _server; }; -#endif /* WEBSOCKETSSERVER_H_ */ +#endif /* WEBSOCKETSSERVER_H_ */ \ No newline at end of file diff --git a/myProfile.json b/myProfile.json index bac5d993..73dd6ba3 100644 --- a/myProfile.json +++ b/myProfile.json @@ -3,29 +3,37 @@ "name": "IoTmanagerVer4", "apssid": "IoTmanager", "appass": "", - "routerssid": "iot", - "routerpass": "hostel3333", + "wifirep_apchanel": 7, + "wifirep_apip": "192.168.4.1", + "wifirep_staip": "192.168.1.160", + "wifirep_netmask": "255.255.255.0", + "wifirep_gateway": "192.168.4.1", + "wifirep_dns": "192.168.4.1", + "routerssid": "HomeNET", + "routerpass": "Wi73Jktu0205", "timezone": 2, "ntp": "pool.ntp.org", "weblogin": "admin", "webpass": "admin", - "mqttServer": "", - "mqttPort": 8021, - "mqttPrefix": "/risenew", + "mqttServer": "192.168.88.10", + "mqttPort": 1883, + "mqttPrefix": "/IoTmanager", "mqttUser": "rise", "mqttPass": "3hostel3", "serverip": "http://iotmanager.org", + "serverlocal": "http://192.168.1.2:5500", "log": 0, "mqttin": 0, - "pinSCL": 0, - "pinSDA": 0, + "pinSCL": 9, + "pinSDA": 8, "i2cFreq": 100000, - "wg": "group1" + "wg": "group1", + "debugTraceMsgTlgrm": 1 }, "projectProp": { "platformio": { - "default_envs": "esp8266_4mb", - "comments_default_envs": "choose from: esp8266_4mb or esp32_4mb or esp32cam_4mb or esp32s2_4mb or esp32_4mb3f or esp32s3_16mb or esp32c3m_4mb or esp8266_1mb or esp8266_1mb_ota or esp8285_1mb or esp8285_1mb_ota", + "default_envs": "esp32_4mb3f", + "comments_default_envs": "choose from: esp8266_4mb, esp32_4mb, esp32_4mb3f, esp8266_16mb, esp32_16mb, esp32cam_4mb, esp32s2_4mb, esp32s3_16mb, esp32c3m_4mb, esp8266_1mb, esp8266_1mb_ota, esp8266_2mb, esp8266_2mb_ota, esp8285_1mb, esp8285_1mb_ota, esp32c6_4mb, esp32c6_8mb, bk7231n, esp32_wifirep", "envs": [ { "name": "esp8266_4mb", @@ -53,6 +61,14 @@ "partitions": "0x8000", "littlefs": "0x310000" }, + { + "name": "esp32_wifirep", + "boot_app0": "0xe000", + "bootloader_qio_80m": "0x1000", + "firmware": "0x10000", + "partitions": "0x8000", + "littlefs": "0x310000" + }, { "name": "esp32cam_4mb", "boot_app0": "0xe000", @@ -122,6 +138,22 @@ "firmware": "0x10000", "partitions": "0x8000", "littlefs": "0x910000" + }, + { + "name": "esp32c6_4mb", + "boot_app0": "0xe000", + "bootloader_qio_80m": "0x1000", + "firmware": "0x10000", + "partitions": "0x8000", + "littlefs": "0x310000" + }, + { + "name": "esp32c6_8mb", + "boot_app0": "0xe000", + "bootloader_qio_80m": "0x1000", + "firmware": "0x10000", + "partitions": "0x8000", + "littlefs": "0x670000" } ] } @@ -136,6 +168,14 @@ "path": "src/modules/virtual/Cron", "active": true }, + { + "path": "src/modules/virtual/DiscoveryHA", + "active": false + }, + { + "path": "src/modules/virtual/DiscoveryHomeD", + "active": false + }, { "path": "src/modules/virtual/GoogleSheet", "active": false @@ -144,10 +184,22 @@ "path": "src/modules/virtual/Loging", "active": true }, + { + "path": "src/modules/virtual/Loging2", + "active": false + }, + { + "path": "src/modules/virtual/Loging3", + "active": false + }, { "path": "src/modules/virtual/LogingDaily", "active": true }, + { + "path": "src/modules/virtual/LogingHourly", + "active": true + }, { "path": "src/modules/virtual/Math", "active": true @@ -164,6 +216,10 @@ "path": "src/modules/virtual/Timer", "active": true }, + { + "path": "src/modules/virtual/UpdateServer", + "active": true + }, { "path": "src/modules/virtual/Variable", "active": true @@ -178,17 +234,13 @@ } ], "sensors": [ - { - "path": "src/modules/exec/Pcf8591", - "active": false - }, { "path": "src/modules/sensors/A02Distance", - "active": true + "active": false }, { "path": "src/modules/sensors/Acs712", - "active": true + "active": false }, { "path": "src/modules/sensors/Ads1115", @@ -208,7 +260,11 @@ }, { "path": "src/modules/sensors/BL0937", - "active": true + "active": false + }, + { + "path": "src/modules/sensors/BL0942", + "active": false }, { "path": "src/modules/sensors/Ble", @@ -242,6 +298,10 @@ "path": "src/modules/sensors/DS2401", "active": false }, + { + "path": "src/modules/sensors/DscKeybus", + "active": true + }, { "path": "src/modules/sensors/Emon", "active": false @@ -300,12 +360,20 @@ }, { "path": "src/modules/sensors/Max6675", - "active": false + "active": true }, { "path": "src/modules/sensors/Mhz19", "active": false }, + { + "path": "src/modules/sensors/ModbusRTU", + "active": false + }, + { + "path": "src/modules/sensors/ModbusRTUasync", + "active": false + }, { "path": "src/modules/sensors/MQgas", "active": true @@ -314,17 +382,21 @@ "path": "src/modules/sensors/Ntc", "active": false }, + { + "path": "src/modules/sensors/Pcf8591", + "active": false + }, { "path": "src/modules/sensors/Pzem004t", "active": false }, { "path": "src/modules/sensors/Pzem004t_v2", - "active": true + "active": false }, { "path": "src/modules/sensors/RCswitch", - "active": false + "active": true }, { "path": "src/modules/sensors/RTC", @@ -332,7 +404,7 @@ }, { "path": "src/modules/sensors/S8", - "active": true + "active": false }, { "path": "src/modules/sensors/Scd40", @@ -348,15 +420,15 @@ }, { "path": "src/modules/sensors/Sht20", - "active": true + "active": false }, { "path": "src/modules/sensors/Sht30", - "active": true + "active": false }, { "path": "src/modules/sensors/Sonar", - "active": true + "active": false }, { "path": "src/modules/sensors/UART", @@ -382,7 +454,11 @@ }, { "path": "src/modules/exec/Buzzer", - "active": true + "active": false + }, + { + "path": "src/modules/exec/EctoControlAdapter", + "active": false }, { "path": "src/modules/exec/Enconder", @@ -402,7 +478,11 @@ }, { "path": "src/modules/exec/IoTServo", - "active": true + "active": false + }, + { + "path": "src/modules/exec/IRremote", + "active": false }, { "path": "src/modules/exec/Mcp23008", @@ -410,11 +490,15 @@ }, { "path": "src/modules/exec/Mcp23017", - "active": true + "active": false + }, + { + "path": "src/modules/exec/MilightHub", + "active":true }, { "path": "src/modules/exec/Mp3", - "active": true + "active": false }, { "path": "src/modules/exec/Multitouch", @@ -426,7 +510,7 @@ }, { "path": "src/modules/exec/Pcf8574", - "active": true + "active": false }, { "path": "src/modules/exec/Pwm32", @@ -434,7 +518,7 @@ }, { "path": "src/modules/exec/Pwm8266", - "active": true + "active": false }, { "path": "src/modules/exec/SDcard", @@ -452,6 +536,10 @@ "path": "src/modules/exec/SysExt", "active": false }, + { + "path": "src/modules/exec/Tca9555", + "active": false + }, { "path": "src/modules/exec/Telegram", "active": false @@ -466,7 +554,7 @@ }, { "path": "src/modules/exec/Thermostat", - "active": false + "active": true }, { "path": "src/modules/sensors/Ds2423", @@ -476,11 +564,19 @@ "screens": [ { "path": "src/modules/display/DwinI", - "active": true + "active": false + }, + { + "path": "src/modules/display/GyverLAMP", + "active": false }, { "path": "src/modules/display/Lcd2004", - "active": true + "active": false + }, + { + "path": "src/modules/display/LedFX", + "active": false }, { "path": "src/modules/display/Nextion", @@ -496,16 +592,20 @@ }, { "path": "src/modules/display/Oled64", - "active": true + "active": false }, { "path": "src/modules/display/Smi2_m", - "active": true + "active": false }, { "path": "src/modules/display/TM16XX", "active": false }, + { + "path": "src/modules/display/U8g2lib", + "active": false + }, { "path": "src/modules/display/Ws2812b", "active": false diff --git a/platformio.ini b/platformio.ini index 82d089cd..a87688e4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,11 +1,21 @@ [platformio] -default_envs = esp8266_4mb +default_envs = esp32_4mb3f data_dir = data_svelte [common_env_data] lib_deps_external = bblanchon/ArduinoJson @6.18.0 knolleary/PubSubClient + gyverlibs/EncButton @ ^2.0 + https://github.com/maxint-rd/TM16xx + https://github.com/adafruit/Adafruit-GFX-Library + adafruit/Adafruit BusIO @ ^1.13.2 + adafruit/Adafruit BME280 Library + adafruit/Adafruit BMP280 Library + beegee-tokyo/DHT sensor library for ESPx + adafruit/MAX6675 library + kitesurfer1404/WS2812FX@^1.4.5 + https://github.com/taligentx/dscKeybusInterface.git [env] extra_scripts = pre:tools/prebuildscript.py @@ -15,7 +25,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8266_1mb_ota_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_1mb_ota="esp8266_1mb_ota" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_1mb_ota="esp8266_1mb_ota" + ${env:esp8266_1mb_ota_fromitems.build_flags} framework = arduino board = nodemcuv2 board_build.ldscript = eagle.flash.1m64.ld @@ -36,7 +49,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8266_1mb_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_1mb="esp8266_1mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_1mb="esp8266_1mb" + ${env:esp8266_1mb_fromitems.build_flags} framework = arduino board = nodemcuv2 board_build.ldscript = eagle.flash.1m256.ld @@ -57,7 +73,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8285_1mb_ota_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_1mb_ota="esp8266_1mb_ota" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_1mb_ota="esp8266_1mb_ota" + ${env:esp8285_1mb_ota_fromitems.build_flags} framework = arduino board = esp8285 board_build.ldscript = eagle.flash.1m64.ld @@ -78,7 +97,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8266_2mb_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_2mb="esp8266_2mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_2mb="esp8266_2mb" + ${env:esp8266_2mb_fromitems.build_flags} framework = arduino board = d1_wroom_02 board_build.ldscript = eagle.flash.2m1m.ld @@ -99,7 +121,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8266_2mb_ota_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_2mb_ota="esp8266_2mb_ota" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_2mb_ota="esp8266_2mb_ota" + ${env:esp8266_2mb_ota_fromitems.build_flags} framework = arduino board = d1_wroom_02 board_build.ldscript = eagle.flash.2m256.ld @@ -120,7 +145,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8285_1mb_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_1mb="esp8266_1mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_1mb="esp8266_1mb" + ${env:esp8285_1mb_fromitems.build_flags} framework = arduino board = esp8285 board_build.ldscript = eagle.flash.1m256.ld @@ -141,7 +169,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8266_4mb_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_4mb="esp8266_4mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_4mb="esp8266_4mb" + ${env:esp8266_4mb_fromitems.build_flags} framework = arduino board = nodemcuv2 board_build.ldscript = eagle.flash.4m1m.ld @@ -163,7 +194,10 @@ lib_deps = ${common_env_data.lib_deps_external} ${env:esp8266_16mb_fromitems.lib_deps} ESPAsyncUDP -build_flags = -Desp8266_16mb="esp8266_16mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp8266_16mb="esp8266_16mb" + ${env:esp8266_16mb_fromitems.build_flags} framework = arduino board = nodemcuv2 platform = espressif8266 @4.0.1 @@ -184,10 +218,14 @@ extra_scripts = pre:tools/patch32_ws.py lib_deps = ${common_env_data.lib_deps_external} ${env:esp32_4mb_fromitems.lib_deps} -build_flags = -Desp32_4mb="esp32_4mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp32_4mb="esp32_4mb" + -Wl,--wrap=esp_panic_handler + ${env:esp32_4mb_fromitems.build_flags} framework = arduino board = esp32dev -platform = espressif32 @5.1.1 +platform = espressif32 @6.6.0 monitor_filters = esp32_exception_decoder upload_speed = 921600 monitor_speed = 115200 @@ -205,10 +243,14 @@ extra_scripts = pre:tools/patch32_ws.py lib_deps = ${common_env_data.lib_deps_external} ${env:esp32_4mb3f_fromitems.lib_deps} -build_flags = -Desp32_4mb="esp32_4mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp32_4mb3f="esp32_4mb3f" + -Wl,--wrap=esp_panic_handler + ${env:esp32_4mb3f_fromitems.build_flags} framework = arduino board = esp32dev -platform = espressif32 @5.1.1 +platform = espressif32 @6.6.0 monitor_filters = esp32_exception_decoder upload_speed = 921600 monitor_speed = 115200 @@ -227,13 +269,16 @@ extra_scripts = pre:tools/patch32_ws.py lib_deps = ${common_env_data.lib_deps_external} ${env:esp32cam_4mb_fromitems.lib_deps} +lib_ignore = LT_WebSockets build_flags = -Desp32cam_4mb="esp32cam_4mb" -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue + -Wl,--wrap=esp_panic_handler + ${env:esp32cam_4mb_fromitems.build_flags} framework = arduino board = esp32cam -platform = espressif32 @5.1.1 +platform = espressif32 @6.6.0 monitor_filters = esp32_exception_decoder upload_speed = 921600 monitor_speed = 115200 @@ -251,13 +296,16 @@ extra_scripts = pre:tools/patch32_ws.py lib_deps = ${common_env_data.lib_deps_external} ${env:esp32s2_4mb_fromitems.lib_deps} +lib_ignore = LT_WebSockets build_flags = -Desp32s2_4mb="esp32s2_4mb" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=0 + -Wl,--wrap=esp_panic_handler + ${env:esp32s2_4mb_fromitems.build_flags} framework = arduino board = lolin_s2_mini -platform = espressif32 @6.3.1 +platform = espressif32 @6.6.0 monitor_filters = esp32_exception_decoder upload_speed = 921600 monitor_speed = 115200 @@ -275,11 +323,14 @@ extra_scripts = pre:tools/patch32_ws.py lib_deps = ${common_env_data.lib_deps_external} ${env:esp32c3m_4mb_fromitems.lib_deps} +lib_ignore = LT_WebSockets build_flags = -Desp32c3m_4mb="esp32c3m_4mb" + -Wl,--wrap=esp_panic_handler + ${env:esp32c3m_4mb_fromitems.build_flags} framework = arduino board = lolin_c3_mini -platform = espressif32 @6.3.1 +platform = espressif32 @6.6.0 monitor_filters = esp32_exception_decoder upload_speed = 921600 monitor_speed = 115200 @@ -298,12 +349,15 @@ extra_scripts = pre:tools/patch32_ws.py lib_deps = ${common_env_data.lib_deps_external} ${env:esp32s3_16mb_fromitems.lib_deps} +lib_ignore = LT_WebSockets build_flags = -Desp32s3_16mb="esp32s3_16mb" + -Wl,--wrap=esp_panic_handler + ${env:esp32s3_16mb_fromitems.build_flags} framework = arduino board = esp32-s3-devkitc-1 board_build.mcu = esp32s3 -platform = espressif32 @6.3.1 +platform = espressif32 @6.6.0 monitor_filters = esp32_exception_decoder upload_speed = 921600 monitor_speed = 115200 @@ -323,10 +377,14 @@ extra_scripts = pre:tools/patch32_ws.py lib_deps = ${common_env_data.lib_deps_external} ${env:esp32_16mb_fromitems.lib_deps} -build_flags = -Desp32_16mb="esp32_16mb" +lib_ignore = LT_WebSockets +build_flags = + -Desp32_16mb="esp32_16mb" + -Wl,--wrap=esp_panic_handler + ${env:esp32_16mb_fromitems.build_flags} framework = arduino board = esp32dev -platform = espressif32 @5.1.1 +platform = espressif32 @6.6.0 monitor_filters = esp32_exception_decoder upload_port = COM11 upload_speed = 921600 @@ -342,6 +400,130 @@ build_src_filter = + ${env:esp32_16mb_fromitems.build_src_filter} +[env:esp32_wifirep] +lib_deps = + ${common_env_data.lib_deps_external} + ${env:esp32_wifirep_fromitems.lib_deps} +lib_ignore = LT_WebSockets +build_flags = + -Desp32_wifirep="esp32_wifirep" + -Wl,--wrap=esp_panic_handler + ${env:esp32_wifirep_fromitems.build_flags} +framework = arduino +board = esp32dev +platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.3/platform-espressif32-2.0.5.3.zip +monitor_filters = esp32_exception_decoder +upload_speed = 921600 +monitor_speed = 115200 +debug_tool = esp-prog +board_build.partitions = tools/partitions_custom.csv +board_build.filesystem = littlefs +build_src_filter = + +<*.cpp> + + + + + + + ${env:esp32_wifirep_fromitems.build_src_filter} + +[env:esp32c6_4mb] +extra_scripts = pre:tools/patch32c6.py +lib_deps = + ${common_env_data.lib_deps_external} + ${env:esp32c6_4mb_fromitems.lib_deps} +lib_ignore = LT_WebSockets +build_flags = + -Desp32c6_4mb="esp32c6_4mb" + -DARDUINO_USB_CDC_ON_BOOT=0 + -DARDUINO_USB_MODE=0 + -Wl,--wrap=esp_panic_handler + ${env:esp32c6_4mb_fromitems.build_flags} +framework = arduino +board = esp32-c6-devkitm-1 +platform = espressif32 @6.9.0 +platform_packages = + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1 + framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip +monitor_filters = esp32_exception_decoder +upload_speed = 921600 +monitor_speed = 115200 +debug_tool = esp-prog +board_build.partitions = tools/partitions_custom.csv +board_build.filesystem = littlefs +build_src_filter = + +<*.cpp> + + + + + + + ${env:esp32c6_4mb_fromitems.build_src_filter} + +[env:esp32c6_8mb] +extra_scripts = pre:tools/patch32c6.py +lib_deps = + ${common_env_data.lib_deps_external} + ${env:esp32c6_8mb_fromitems.lib_deps} +lib_ignore = LT_WebSockets +build_flags = + -Desp32c6_8mb="esp32c6_8mb" + -DARDUINO_USB_CDC_ON_BOOT=0 + -DARDUINO_USB_MODE=0 + -Wl,--wrap=esp_panic_handler + ${env:esp32c6_8mb_fromitems.build_flags} +framework = arduino +board = esp32-c6-devkitm-1 +platform = espressif32 @6.9.0 +platform_packages = + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1 + framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip +monitor_filters = esp32_exception_decoder +upload_speed = 921600 +monitor_speed = 115200 +debug_tool = esp-prog +board_build.partitions = tools/partitions_custom_8mb.csv +board_upload.flash_size = 8MB +board_build.filesystem = littlefs +build_src_filter = + +<*.cpp> + + + + + + + ${env:esp32c6_8mb_fromitems.build_src_filter} + +[env:bk7231n] +extra_scripts = + pre:tools/lt_fsbuild.py + pre:tools/lt_fsflash.py +lib_compat_mode = off +lib_deps = + LT_WebSockets + https://github.com/Mit4el/ESPAsyncUDP#master + ${common_env_data.lib_deps_external} + ${env:bk7231n_fromitems.lib_deps} +lib_ignore = EspSoftwareSerial, HTTPUpdate, WebSockets +platform = https://github.com/Mit4el/libretiny#master +framework = arduino +board = generic-bk7231n-qfn32-tuya +custom_fw_name = iotm_tiny +custom_fw_version = 0.0.1 +upload_speed = 921600 +monitor_speed = 115200 +build_flags = + -Dbk7231n="bk7231n" + -DLT_LOGLEVEL=LT_LEVEL_FATAL + -DLT_DEBUG_ALL=1 + -DPROJECT_DATA_DIR="data_svelte" + -DLT_USE_TIME=1 + -DDEBUG_ESP_PORT=Serial1 + -DNODEBUG_WEBSOCKETS=1 + -DLT_UART_DEFAULT_PORT=1 + -DUPLOAD_PORT=auto + ${env:bk7231n_fromitems.build_flags} +build_src_filter = + +<*.cpp> + + + + + + + ${env:bk7231n_fromitems.build_src_filter} + [env:esp8266_1mb_ota_fromitems] lib_deps = adafruit/Adafruit BME280 Library @@ -352,8 +534,6 @@ lib_deps = WEMOS SHT3x@1.0.0 plerup/EspSoftwareSerial adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - adafruit/Adafruit BusIO @ ^1.13.2 https://github.com/robotclass/RobotClass_LiquidCrystal_I2C marcoschwartz/LiquidCrystal_I2C@^1.1.4 build_src_filter = @@ -396,8 +576,6 @@ lib_deps = plerup/EspSoftwareSerial gyverlibs/EncButton @ ^2.0 adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - adafruit/Adafruit BusIO @ ^1.13.2 https://github.com/robotclass/RobotClass_LiquidCrystal_I2C marcoschwartz/LiquidCrystal_I2C@^1.1.4 build_src_filter = @@ -469,8 +647,6 @@ build_src_filter = lib_deps = milesburton/DallasTemperature@^3.9.1 adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - adafruit/Adafruit BusIO @ ^1.13.2 marcoschwartz/LiquidCrystal_I2C@^1.1.4 build_src_filter = + @@ -499,8 +675,6 @@ lib_deps = plerup/EspSoftwareSerial gyverlibs/EncButton @ ^2.0 adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - adafruit/Adafruit BusIO @ ^1.13.2 https://github.com/robotclass/RobotClass_LiquidCrystal_I2C marcoschwartz/LiquidCrystal_I2C@^1.1.4 build_src_filter = @@ -544,62 +718,40 @@ lib_deps = adafruit/Adafruit BMP280 Library beegee-tokyo/DHT sensor library for ESPx https://github.com/milesburton/Arduino-Temperature-Control-Library - robtillaart/SHT2x@^0.1.1 - WEMOS SHT3x@1.0.0 plerup/EspSoftwareSerial gyverlibs/EncButton @ ^2.0 - adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - dfrobot/DFRobotDFPlayerMini @ ^1.0.5 - adafruit/Adafruit BusIO @ ^1.13.2 - plerup/EspSoftwareSerial - https://github.com/robotclass/RobotClass_LiquidCrystal_I2C - marcoschwartz/LiquidCrystal_I2C@^1.1.4 - https://github.com/stblassitude/Adafruit_SSD1306_Wemos_OLED - https://github.com/adafruit/Adafruit-GFX-Library build_src_filter = + + + + + + + + + + + + + - + - + + + - + + + + + + - + - + + + + + + - + - + - + - + + + + + + + + + - + - + - + + - + - + + - + - + - + + + [env:esp32_4mb_fromitems] lib_deps = @@ -608,31 +760,20 @@ lib_deps = adafruit/Adafruit BMP280 Library beegee-tokyo/DHT sensor library for ESPx https://github.com/milesburton/Arduino-Temperature-Control-Library - robtillaart/SHT2x@^0.1.1 - WEMOS SHT3x@1.0.0 plerup/EspSoftwareSerial gyverlibs/EncButton @ ^2.0 - https://github.com/RoboticsBrno/ServoESP32#v1.0.3 - adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - dfrobot/DFRobotDFPlayerMini @ ^1.0.5 - adafruit/Adafruit BusIO @ ^1.13.2 - https://github.com/robotclass/RobotClass_LiquidCrystal_I2C - marcoschwartz/LiquidCrystal_I2C@^1.1.4 - https://github.com/stblassitude/Adafruit_SSD1306_Wemos_OLED - https://github.com/adafruit/Adafruit-GFX-Library - https://github.com/maxint-rd/TM16xx - adafruit/Adafruit GFX Library @ ^1.11.5 - adafruit/Adafruit BusIO @ ^1.13.2 build_src_filter = + + + + + + + + + + + + + + + - + + + + @@ -640,29 +781,20 @@ build_src_filter = + + + - + + + + + + - + - + - + - + + + + + + + + + + - + - + - + + - + - + + + - + - + - + - + + + [env:esp32_4mb3f_fromitems] lib_deps = @@ -670,30 +802,21 @@ lib_deps = adafruit/Adafruit BME280 Library adafruit/Adafruit BMP280 Library beegee-tokyo/DHT sensor library for ESPx - https://github.com/milesburton/Arduino-Temperature-Control-Library - https://github.com/tremaru/iarduino_RTC - robtillaart/SHT2x@^0.1.1 - WEMOS SHT3x@1.0.0 + milesburton/DallasTemperature @ ^4.0.4 plerup/EspSoftwareSerial gyverlibs/EncButton @ ^2.0 - https://github.com/RoboticsBrno/ServoESP32#v1.0.3 - adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - dfrobot/DFRobotDFPlayerMini @ ^1.0.5 - adafruit/Adafruit BusIO @ ^1.13.2 - https://github.com/robotclass/RobotClass_LiquidCrystal_I2C - marcoschwartz/LiquidCrystal_I2C@^1.1.4 - https://github.com/maxint-rd/TM16xx - adafruit/Adafruit GFX Library @ ^1.11.5 build_src_filter = + + + + + + + + + + + + + + + - + + - + + + + @@ -701,27 +824,20 @@ build_src_filter = + + + - + + + + - + - + - + - + + + + + + - + + - + - + - + + - + + + - + - + - + + + +build_flags = + -Dmod_RtcDriver + -DhardRTC=1 [env:esp32cam_4mb_fromitems] lib_deps = @@ -754,19 +870,47 @@ build_src_filter = [env:esp8266_16mb_fromitems] lib_deps = + https://github.com/dancol90/ESP8266Ping + https://github.com/enjoyneering/AHTxx.git + adafruit/Adafruit BME280 Library + adafruit/Adafruit BMP280 Library + beegee-tokyo/DHT sensor library for ESPx + https://github.com/milesburton/Arduino-Temperature-Control-Library + plerup/EspSoftwareSerial gyverlibs/EncButton @ ^2.0 build_src_filter = + + + + + + + + + + - + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [env:esp32_16mb_fromitems] lib_deps = @@ -782,15 +926,52 @@ build_src_filter = [env:esp32c3m_4mb_fromitems] lib_deps = + https://github.com/enjoyneering/AHTxx.git + adafruit/Adafruit BME280 Library + adafruit/Adafruit BMP280 Library + beegee-tokyo/DHT sensor library for ESPx + milesburton/DallasTemperature @ ^4.0.4 + adafruit/MAX6675 library + plerup/EspSoftwareSerial + gyverlibs/EncButton @ ^2.0 + https://github.com/maxint-rd/TM16xx + https://github.com/adafruit/Adafruit-GFX-Library + adafruit/Adafruit BusIO @ ^1.13.2 build_src_filter = + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +build_flags = + -Dmod_RtcDriver + -DhardRTC=1 [env:esp32s3_16mb_fromitems] lib_deps = @@ -804,3 +985,148 @@ build_src_filter = + + +[env:esp32_wifirep_fromitems] +lib_deps = + https://github.com/enjoyneering/AHTxx.git + adafruit/Adafruit BME280 Library + adafruit/Adafruit BMP280 Library + beegee-tokyo/DHT sensor library for ESPx + https://github.com/milesburton/Arduino-Temperature-Control-Library + plerup/EspSoftwareSerial + gyverlibs/EncButton @ ^2.0 +build_src_filter = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[env:esp32c6_4mb_fromitems] +lib_deps = + https://github.com/enjoyneering/AHTxx.git + adafruit/Adafruit BME280 Library + adafruit/Adafruit BMP280 Library + beegee-tokyo/DHT sensor library for ESPx + https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg + plerup/EspSoftwareSerial + gyverlibs/EncButton @ ^2.0 +build_src_filter = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[env:esp32c6_8mb_fromitems] +lib_deps = + https://github.com/enjoyneering/AHTxx.git + adafruit/Adafruit BME280 Library + adafruit/Adafruit BMP280 Library + beegee-tokyo/DHT sensor library for ESPx + https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg + plerup/EspSoftwareSerial + gyverlibs/EncButton @ ^2.0 +build_src_filter = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[env:bk7231n_fromitems] +lib_deps = +build_src_filter = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/run.py b/run.py new file mode 100644 index 00000000..44edf8f5 --- /dev/null +++ b/run.py @@ -0,0 +1,111 @@ +# Скрипт для простой прошивки ESP с учетом профиля из myProfile.json и поиска нужной кнопочки в интерфейсе PlatformIO +# Если ничего не указано в параметрах, то выполняется последовательно набор команд: +# 1. run PrepareProject.py +# 2. platformio run -t clean +# 3. platformio run -t uploadfs -е default_envs +# 4. platformio run -t upload -е default_envs +# 5. platformio run -t monitor +# где default_envs - это параметр default_envs из myProfile.json +# +# Если указан параметр -p или --profile <ИмяФайла>, то выполняется первая команда PrepareProject.py -p <ИмяФайла> +# Если указан параметр -b или --board , то выполняется первая команда PrepareProject.py -b +# Если указан парамтер -l или --lite, то пропускаются команды 1, 2 и 5 с предварительной компиляцией +# Если указан параметр -d или --debug, то выполняется только команда 4 с предварительной компиляцией + +import os +import subprocess +import sys +import json + +def get_platformio_path(): + """ + Возвращает путь к PlatformIO в зависимости от операционной системы. + """ + if os.name == 'nt': # Windows + return os.path.join(os.environ['USERPROFILE'], '.platformio', 'penv', 'Scripts', 'pio.exe') + else: # Linux/MacOS + return os.path.join(os.environ['HOME'], '.platformio', 'penv', 'bin', 'pio') + +def load_default_envs(profile_path="myProfile.json"): + """ + Загружает значение default_envs из файла myProfile.json. + """ + if not os.path.isfile(profile_path): + print(f"Файл профиля {profile_path} не найден.") + sys.exit(1) + + try: + with open(profile_path, 'r', encoding='utf-8') as file: + profile_data = json.load(file) + return profile_data["projectProp"]["platformio"]["default_envs"] + except KeyError: + print("Не удалось найти ключ 'default_envs' в myProfile.json.") + sys.exit(1) + except json.JSONDecodeError: + print(f"Ошибка при чтении файла {profile_path}: некорректный JSON.") + sys.exit(1) + +def run_command(command): + """ + Выполняет указанную команду в subprocess. + """ + try: + print(f"Выполнение команды: {' '.join(command)}") + subprocess.run(command, check=True) + except subprocess.CalledProcessError as e: + print(f"Ошибка при выполнении команды: {e}") + sys.exit(e.returncode) + +def run_platformio(): + """ + Основная логика выполнения команд в зависимости от параметров. + """ + pio_path = get_platformio_path() + + # Проверяем, существует ли PlatformIO + if not os.path.isfile(pio_path): + print(f"PlatformIO не найден по пути: {pio_path}") + sys.exit(1) + # print(f"PlatformIO найден по пути: {pio_path}") + + # Читаем аргументы командной строки + args = sys.argv[1:] + lite_mode = '-l' in args or '--lite' in args + debug_mode = '-d' in args or '--debug' in args + profile_index = next((i for i, arg in enumerate(args) if arg in ('-p', '--profile')), None) + profile_file = args[profile_index + 1] if profile_index is not None and len(args) > profile_index + 1 else "myProfile.json" + + # Загружаем default_envs из myProfile.json, если не указан параметр -b, который имеет больший приоритет + board_index = next((i for i, arg in enumerate(args) if arg in ('-b', '--board')), None) + default_envs = args[board_index + 1] if board_index is not None and len(args) > board_index + 1 else load_default_envs(profile_path=profile_file) + + print(f"Используем default_envs: {default_envs}") + print(f"Режим Lite: {lite_mode}, Режим отладки: {debug_mode}") + print(f"Профиль: {profile_file}") + + # Выполнение команд в зависимости от параметров + if not lite_mode and not debug_mode: + # Полный набор команд + run_command(['python', 'PrepareProject.py', '-p', profile_file]) + + # Добавляем сообщение о необходимости дождаться завершения обновления конфигурации + input(f"\x1b[1;31;42m Подождите завершения обновления конфигурации PlatformIO, затем нажмите Ввод для продолжения...\x1b[0m") + + run_command([pio_path, 'run', '-t', 'clean']) + run_command([pio_path, 'run', '-t', 'uploadfs', '--environment', default_envs]) + run_command([pio_path, 'run', '-t', 'upload', '--environment', default_envs]) + run_command([pio_path, 'run', '-t', 'monitor']) + elif lite_mode: + # Упрощенный режим (пропускаем команды 1, 2 и 5) + run_command([pio_path, 'run', '-t', 'buildfs', '--environment', default_envs]) + run_command([pio_path, 'run', '-t', 'uploadfs', '--environment', default_envs]) + run_command([pio_path, 'run', '--environment', default_envs]) + run_command([pio_path, 'run', '-t', 'upload', '--environment', default_envs]) + elif debug_mode: + # Режим отладки (только команда 4) + run_command([pio_path, 'run', '--environment', default_envs]) + run_command([pio_path, 'run', '-t', 'upload', '--environment', default_envs]) + +if __name__ == "__main__": + run_platformio() + diff --git a/src/DebugTrace.cpp b/src/DebugTrace.cpp new file mode 100644 index 00000000..49cb0239 --- /dev/null +++ b/src/DebugTrace.cpp @@ -0,0 +1,352 @@ +#include "DebugTrace.h" +#if defined(RESTART_DEBUG_INFO) +// #ifdef RESTART_DEBUG_INFO +__NOINIT_ATTR static re_restart_debug_t _debug_info; + +#include "esp_debug_helpers.h" +#include "esp_types.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "soc/soc_memory_layout.h" +#include "soc/cpu.h" + +// RU: Размер буфера для конвертации даты и времeни в строку +#define CONFIG_FORMAT_STRFTIME_BUFFER_SIZE 32 +#define CONFIG_FORMAT_STRFTIME_DTS_BUFFER_SIZE 20 // YYYY.MM.DD HH:NN:SS + \n + +// RU: Форматы даты и времени +#define CONFIG_FORMAT_DTS "%d.%m.%Y %H:%M:%S" + +void IRAM_ATTR debugHeapUpdate() +{ + _debug_info.heap_total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); + _debug_info.heap_free = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + size_t _new_free_min = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + if ((_debug_info.heap_free_min == 0) || (_new_free_min < _debug_info.heap_free_min)) + { + _debug_info.heap_free_min = _new_free_min; + _debug_info.heap_min_time = time(nullptr); + }; +} + +void IRAM_ATTR debugBacktraceUpdate() +{ + esp_backtrace_frame_t stk_frame; + esp_backtrace_get_start(&(stk_frame.pc), &(stk_frame.sp), &(stk_frame.next_pc)); + _debug_info.backtrace[0] = esp_cpu_process_stack_pc(stk_frame.pc); + + bool corrupted = (esp_stack_ptr_is_sane(stk_frame.sp) && + esp_ptr_executable((void *)esp_cpu_process_stack_pc(stk_frame.pc))) + ? false + : true; + + uint8_t i = CONFIG_RESTART_DEBUG_STACK_DEPTH; + while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) + { + if (!esp_backtrace_get_next_frame(&stk_frame)) + { + corrupted = true; + }; + _debug_info.backtrace[CONFIG_RESTART_DEBUG_STACK_DEPTH - i] = esp_cpu_process_stack_pc(stk_frame.pc); + }; +} + +void IRAM_ATTR debugUpdate() +{ + debugHeapUpdate(); + debugBacktraceUpdate(); +} + +extern "C" void __wrap_esp_panic_handler(void *info) +{ + + debugHeapUpdate(); + debugBacktraceUpdate(); + bootloop_panic_count += 1; + // Call the original panic handler function to finish processing this error (creating a core dump for example...) + __real_esp_panic_handler(info); +} + +re_restart_debug_t debugGet() +{ + re_restart_debug_t ret; + memset(&ret, 0, sizeof(re_restart_debug_t)); + esp_reset_reason_t esp_reason = esp_reset_reason(); + if ((esp_reason != ESP_RST_UNKNOWN) && (esp_reason != ESP_RST_POWERON)) + { + uint8_t i = CONFIG_RESTART_DEBUG_STACK_DEPTH; + ret = _debug_info; + if (_debug_info.heap_total > heap_caps_get_total_size(MALLOC_CAP_DEFAULT)) + { + memset(&ret, 0, sizeof(re_restart_debug_t)); + }; + }; + memset(&_debug_info, 0, sizeof(re_restart_debug_t)); + return ret; +} + +#define CONFIG_MESSAGE_TG_VERSION_DEF "! Устройство запущено\n\nИмя устройства: %s\nПричина перезапуска: %s\nCPU0: %s\nCPU1: %s" +#define CONFIG_MESSAGE_TG_VERSION_HEAP "! Устройство аварийно перезапущено !\n\nИмя устройства: %s\nПричина перезапуска: %s\nCPU0: %s\nCPU1: %s\nHEAP: %s" +#define CONFIG_MESSAGE_TG_VERSION_TRACE "! Устройство аварийно перезапущено !\n\nИмя устройства: %s\nПричина перезапуска: %s\nCPU0: %s\nCPU1: %s\nHEAP: %s\nTRACE: %s" + +#define INFO_MESSAGE_DEBUG "By used -> USERPROFILE/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin xtensa-esp32-elf-addr2line.exe -pfiaC -e .pio/build/esp32_4mb3f/firmware.elf Стэк_адресов" + +char *malloc_stringf(const char *format, ...) +{ + char *ret = nullptr; + if (format != nullptr) + { + // get the list of arguments + va_list args1, args2; + va_start(args1, format); + va_copy(args2, args1); + // calculate length of resulting string + int len = vsnprintf(nullptr, 0, format, args1); + va_end(args1); + // allocate memory for string + if (len > 0) + { +#if USE_ESP_MALLOC + ret = (char *)esp_malloc(len + 1); +#else + ret = (char *)malloc(len + 1); +#endif + if (ret != nullptr) + { + memset(ret, 0, len + 1); + vsnprintf(ret, len + 1, format, args2); + } + else + { + // rlog_e(tagHEAP, "Failed to format string: out of memory!"); + }; + }; + va_end(args2); + }; + return ret; +} + +static char *statesGetDebugHeap(re_restart_debug_t *debug) +{ + if ((debug->heap_total > 0) && (debug->heap_total > debug->heap_free)) + { + struct tm timeinfo; + localtime_r(&debug->heap_min_time, &timeinfo); + char time_buffer[CONFIG_FORMAT_STRFTIME_DTS_BUFFER_SIZE]; + memset(&time_buffer, 0, CONFIG_FORMAT_STRFTIME_DTS_BUFFER_SIZE); + strftime(time_buffer, CONFIG_FORMAT_STRFTIME_DTS_BUFFER_SIZE, CONFIG_FORMAT_DTS, &timeinfo); + + double heapTotal = (double)debug->heap_total / 1024; + double heapFree = (double)debug->heap_free / 1024; + double heapFreeMin = (double)debug->heap_free_min / 1024; + + return malloc_stringf("Total %.1fkB ; Free %.1fkB (%.1f%%) ; FreeMin %.1fkB (%.1f%%) %s", + heapTotal, + heapFree, 100.0 * (heapFree / heapTotal), + heapFreeMin, 100.0 * (heapFreeMin / heapTotal), time_buffer); + }; + return nullptr; +} + +static char *statesGetDebugTrace(re_restart_debug_t *debug) +{ + char *backtrace = nullptr; + char *item = nullptr; + char *temp = nullptr; + for (uint8_t i = 0; i < CONFIG_RESTART_DEBUG_STACK_DEPTH; i++) + { + if (debug->backtrace[i] != 0) + { + item = malloc_stringf("0x%08x", debug->backtrace[i]); + if (item) + { + if (backtrace) + { + temp = backtrace; + backtrace = malloc_stringf("%s %s", temp, item); + free(item); + free(temp); + } + else + { + backtrace = item; + }; + item = nullptr; + }; + } + else + { + break; + } + }; + return backtrace; +} + +void printDebugTrace() +{ + // esp_register_shutdown_handler(debugUpdate); + re_restart_debug_t debug = debugGet(); + char *debug_heap = statesGetDebugHeap(&debug); + char *debug_trace = nullptr; + if (debug_heap) + { + debug_trace = statesGetDebugTrace(&debug); + if (debug_trace) + { + Serial.printf(CONFIG_MESSAGE_TG_VERSION_TRACE, + jsonReadStr(settingsFlashJson, F("name")), ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str(), + debug_heap, debug_trace); + // free(debug_trace); + } + else + { + Serial.printf(CONFIG_MESSAGE_TG_VERSION_HEAP, + jsonReadStr(settingsFlashJson, F("name")), ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str(), + debug_heap); + }; + // free(debug_heap); + } + else + { + // Serial.println("DEVICE START"); + Serial.printf(CONFIG_MESSAGE_TG_VERSION_DEF, + jsonReadStr(settingsFlashJson, F("name")), ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str()); + } + + Serial.println(INFO_MESSAGE_DEBUG); +} + +void sendDebugTraceAndFreeMemory(bool postMsg) +{ + // esp_register_shutdown_handler(debugUpdate); + re_restart_debug_t debug = debugGet(); + char *debug_heap = statesGetDebugHeap(&debug); + char *debug_trace = nullptr; + + if (debug_heap) + { + if (isNetworkActive() && postMsg) + { + debug_trace = statesGetDebugTrace(&debug); + if (debug_trace) + { + if (tlgrmItem) + { + char *msg; + msg = malloc_stringf(CONFIG_MESSAGE_TG_VERSION_TRACE, + jsonReadStr(settingsFlashJson, F("name")).c_str(), ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str(), + debug_heap, debug_trace); + tlgrmItem->sendTelegramMsg(false, String(msg)); + tlgrmItem->sendTelegramMsg(false, String("Подробности /helpDebug в Telegram_v2")); + free(msg); + } + free(debug_trace); + } + else + { + /* + Serial.printf(CONFIG_MESSAGE_TG_VERSION_HEAP, + jsonReadStr(settingsFlashJson, F("name")), ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str(), + debug_heap); + */ + if (tlgrmItem) + { + char *msg; + msg = malloc_stringf(CONFIG_MESSAGE_TG_VERSION_HEAP, + jsonReadStr(settingsFlashJson, F("name")).c_str(), ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str(), + debug_heap); + tlgrmItem->sendTelegramMsg(false, String(msg)); + tlgrmItem->sendTelegramMsg(false, String("Подробности /helpDebug в Telegram_v2")); + free(msg); + } + } + } + free(debug_heap); + } + /* else + { + // Serial.println("DEVICE START"); + // Serial.printf(CONFIG_MESSAGE_TG_VERSION_DEF, + // FIRMWARE_VERSION, ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str()); + if (tlgrmItem && isNetworkActive()) + { + char *msg; + msg = malloc_stringf(CONFIG_MESSAGE_TG_VERSION_DEF, + WiFi.localIP().toString(), FIRMWARE_VERSION, ESP_getResetReason().c_str(), ESP32GetResetReason(0).c_str(), ESP32GetResetReason(1).c_str()); + tlgrmItem->sendTelegramMsg(false, String(msg)); + free(msg); + } + };*/ +} +#else // RESTART_DEBUG_INFO +void printDebugTrace() {} +void sendDebugTraceAndFreeMemory(bool) {} +//void IRAM_ATTR debugUpdate() {} +#if !defined(esp32c6_4mb) && !defined(esp32c6_8mb) +extern "C" void __wrap_esp_panic_handler(void *info) +{ + // Call the original panic handler function to finish processing this error (creating a core dump for example...) + __real_esp_panic_handler(info); +} +#endif //esp32c6 +#endif // !RESTART_DEBUG_INFO + +#if defined(ESP32) + +#include "esp_ota_ops.h" + +#include +// 3 seconds WDT, reset in 1 seconds +#define WDT_TIMEOUT 180 + +void startWatchDog() +{ +#if !defined(esp32c6_4mb) && !defined(esp32c6_8mb) //TODO esp32-c6 переписать esp_task_wdt_init + esp_task_wdt_init(WDT_TIMEOUT, true); // enable panic so ESP32 restarts + esp_task_wdt_add(NULL); // add current thread to WDT watch +#endif +} + +extern "C" bool verifyRollbackLater() +{ + ets_printf("[SYSTEM] - verifyRollbackLater OVERRIDDEN FUNCTION!\n"); + return true; +} + +void verifyFirmware() +{ + Serial.printf("[SYSTEM] - Checking firmware...\n"); + const esp_partition_t *running = esp_ota_get_running_partition(); + esp_ota_img_states_t ota_state; + if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) + { + const char *otaState = ota_state == ESP_OTA_IMG_NEW ? "ESP_OTA_IMG_NEW" + : ota_state == ESP_OTA_IMG_PENDING_VERIFY ? "ESP_OTA_IMG_PENDING_VERIFY" + : ota_state == ESP_OTA_IMG_VALID ? "ESP_OTA_IMG_VALID" + : ota_state == ESP_OTA_IMG_INVALID ? "ESP_OTA_IMG_INVALID" + : ota_state == ESP_OTA_IMG_ABORTED ? "ESP_OTA_IMG_ABORTED" + : "ESP_OTA_IMG_UNDEFINED"; + Serial.printf("[SYSTEM] - Ota state: %s\n", otaState); + + if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) + { + if (esp_ota_mark_app_valid_cancel_rollback() == ESP_OK) + { + Serial.printf("[SYSTEM] - App is valid, rollback cancelled successfully\n"); + } + else + { + Serial.printf("[SYSTEM] - Failed to cancel rollback\n"); + } + } + } + else + { + Serial.printf("[SYSTEM] - OTA partition has no record in OTA data\n"); + } +} +#else //ESP32 +void startWatchDog() {} +//extern "C" bool verifyRollbackLater() {} +void verifyFirmware() {} +#endif \ No newline at end of file diff --git a/src/DeviceList.cpp b/src/DeviceList.cpp index 96903e16..eab59655 100644 --- a/src/DeviceList.cpp +++ b/src/DeviceList.cpp @@ -37,7 +37,11 @@ void addThisDeviceToList() { AsyncUDP asyncUdp; void udpListningInit() { - if (asyncUdp.listenMulticast(IPAddress(239, 255, 255, 255), 4210)) { +#if defined(LIBRETINY) + if (asyncUdp.listenMulticast(IPAddress(239, 255, 255, 255), 4210 , WiFi.localIP() )) { +#else + if (asyncUdp.listenMulticast(IPAddress(239, 255, 255, 255), 4210)) { +#endif asyncUdp.onPacket([](AsyncUDPPacket packet) { // если был включен автоматический поиск устройств то начнем запись в оперативную память if (jsonReadInt(settingsFlashJson, F("udps")) != 0) { @@ -65,8 +69,12 @@ void udpListningInit() { String loacalWorkgroup = ""; jsonRead(settingsFlashJson, F("wg"), loacalWorkgroup); if (remoteWorkgroup == loacalWorkgroup) { +#if defined(LIBRETINY) + SerialPrint("i", F("UDP"), "IP: " + ipToString(packet.remoteIP()) + ":" + String(packet.remotePort()) + " localIP:"+String(packet.localIP())); +#else SerialPrint("i", F("UDP"), "IP: " + packet.remoteIP().toString() + ":" + String(packet.remotePort())); - jsonMergeArrays(devListHeapJson, data); +#endif + jsonMergeArrays(devListHeapJson, data); // эксперементальный вариант отправки нового списка сразу по приходу // sendStringToWs("devlis", devListHeapJson, -1); } @@ -91,7 +99,7 @@ void udpListningInit() { void udpBroadcastInit() { // будем отправлять каждые 60 секунд презентацию данного устройства ts.add( - UDP, 60000, [&](void*) { // UDPP + UDPt, 60000, [&](void*) { // UDPP if (isNetworkActive()) { SerialPrint("i", F("UDP"), F("Broadcast device presentation")); asyncUdp.broadcastTo(getThisDevice().c_str(), 4210); diff --git a/src/ESPConfiguration.cpp b/src/ESPConfiguration.cpp index fa15b2a1..83984386 100644 --- a/src/ESPConfiguration.cpp +++ b/src/ESPConfiguration.cpp @@ -1,5 +1,6 @@ #include "ESPConfiguration.h" #include "classes/IoTGpio.h" +//#include "classes/IoTDiscovery.h" extern IoTGpio IoTgpio; @@ -8,6 +9,10 @@ void* getAPI(String subtype, String params); void configure(String path) { File file = seekFile(path); + if (!file) { + SerialPrint(F("E"), F("FS"), F("configure file open error")); + return; + } file.find("["); while (file.available()) { String jsonArrayElement = file.readStringUntil('}') + "}"; @@ -18,6 +23,7 @@ void configure(String path) { jsonArrayElement = ""; } if (jsonArrayElement != "") { + IoTItem* myIoTItem; String subtype; if (!jsonRead(jsonArrayElement, F("subtype"), subtype)) { //если нет такого ключа в представленном json или он не валидный SerialPrint(F("E"), F("Config"), "json error " + subtype); @@ -29,13 +35,18 @@ void configure(String path) { void* driver; // пробуем спросить драйвер GPIO if (driver = myIoTItem->getGpioDriver()) IoTgpio.regDriver((IoTGpio*)driver); +#ifdef mod_RtcDriver // пробуем спросить драйвер RTC if (driver = myIoTItem->getRtcDriver()) rtcItem = (IoTItem*)driver; +#endif // пробуем спросить драйвер CAM //if (driver = myIoTItem->getCAMDriver()) camItem = (IoTItem*)driver; // пробуем спросить драйвер Benchmark if (driver = myIoTItem->getBenchmarkTask()) benchTaskItem = ((IoTBench*)driver); if (driver = myIoTItem->getBenchmarkLoad()) benchLoadItem = ((IoTBench*)driver); + // пробуем спросить драйвер для интеграций + if (driver = myIoTItem->getHOMEdDiscovery()) HOMEdDiscovery = ((IoTDiscovery*)driver); + if (driver = myIoTItem->getHADiscovery()) HADiscovery = ((IoTDiscovery*)driver); // пробуем спросить драйвер Telegram_v2 if (driver = myIoTItem->getTlgrmDriver()) tlgrmItem = (IoTItem*)driver; IoTItems.push_back(myIoTItem); @@ -45,22 +56,48 @@ void configure(String path) { } file.close(); SerialPrint("i", "Config", "Configured"); +/* +#ifdef ESP32 + if(HOMEdDiscovery) + HOMEdDiscovery->mqttSubscribeDiscovery(); + if(HADiscovery) + HADiscovery->mqttSubscribeDiscovery(); + // оттправляем все статусы + if(HOMEdDiscovery || HADiscovery) + { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->iAmLocal) + { + publishStatusMqtt((*it)->getID(), (*it)->getValue()); + (*it)->onMqttWsAppConnectEvent(); + } + } + } +#endif +*/ } void clearConfigure() { Serial.printf("Start clearing config\n"); +#ifdef mod_RtcDriver rtcItem = nullptr; +#endif //camItem = nullptr; tlgrmItem = nullptr; IoTgpio.clearDrivers(); - for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { Serial.printf("Start delete iotitem %s \n", (*it)->getID().c_str()); if (*it) delete *it; } IoTItems.clear(); - +#ifdef LIBRETINY + valuesFlashJson.remove(0, valuesFlashJson.length()); +#else valuesFlashJson.clear(); +#endif benchTaskItem = nullptr; benchLoadItem = nullptr; + HOMEdDiscovery = nullptr; + HADiscovery = nullptr; } \ No newline at end of file diff --git a/src/EspFileSystem.cpp b/src/EspFileSystem.cpp index fc4892c5..cebead18 100644 --- a/src/EspFileSystem.cpp +++ b/src/EspFileSystem.cpp @@ -1,5 +1,8 @@ #include "EspFileSystem.h" #include "Global.h" +#if defined(esp32c6_4mb) || defined(esp32c6_8mb) +#include "esp_mac.h" +#endif bool fileSystemInit() { @@ -18,14 +21,17 @@ void globalVarsSync() valuesFlashJson = readFile(F("values.json"), 4096); valuesFlashJson.replace("\r\n", ""); - + if (settingsFlashJson == "failed") + return; mqttPrefix = jsonReadStr(settingsFlashJson, F("mqttPrefix")); jsonWriteStr_(settingsFlashJson, "id", chipId); mqttRootDevice = mqttPrefix + "/" + chipId; - +#ifdef LIBRETINY + jsonWriteStr_(settingsFlashJson, "ip", ipToString(WiFi.localIP())); +#else jsonWriteStr_(settingsFlashJson, "ip", WiFi.localIP().toString()); - +#endif // это не используется - удалить в последствии jsonWriteStr_(settingsFlashJson, "root", mqttRootDevice); } @@ -35,6 +41,25 @@ void syncSettingsFlashJson() writeFile(F("settings.json"), settingsFlashJson); } +void resetSettingsFlashByPanic() +{ + FileFS.rename("/config.json", "/config_bak.json"); + /* + update.configJson = readFile("config.json", 4096 * 4); + update.layoutJson = readFile("layout.json", 4096 * 4); + update.scenarioTxt = readFile("scenario.txt", 4096 * 4); + writeFile(F("/config_bak.json"), update.configJson); + writeFile(F("/scenario_bak.txt"), update.scenarioTxt); + writeFile(F("/layout_bak.json"), update.layoutJson); + */ + //update.configJson = "[]"; + //update.scenarioTxt = ""; + //update.layoutJson = "[]"; + writeFile(F("/config.json"), "[]"); + writeFile(F("/scenario.txt"), ""); + writeFile(F("/layout.json"), "[]"); +} + void syncValuesFlashJson() { writeFile(F("values.json"), valuesFlashJson); @@ -79,7 +104,7 @@ uint32_t ESP_getChipId(void) #endif } -// устарела используем новую функцию ниже +/*// устарела используем новую функцию ниже #if !defined(esp32s2_4mb) && !defined(esp32c3m_4mb) && !defined(esp32s3_16mb) //#ifndef esp32s2_4mb uint32_t ESP_getFlashChipId(void) @@ -93,6 +118,7 @@ uint32_t ESP_getFlashChipId(void) #endif } #endif +*/ // https://github.com/espressif/arduino-esp32/issues/6945#issuecomment-1199900892 // получение flash ch id из проекта esp easy @@ -113,7 +139,7 @@ uint32_t getFlashChipIdNew() } // esp_flash_read_id(nullptr, &flashChipId); -#elif defined(ESP8266) +#elif defined(ESP8266) || defined(LIBRETINY) flashChipId = ESP.getFlashChipId(); #endif // ifdef ESP32 } @@ -126,10 +152,13 @@ const String getMacAddress() char buf[13] = { 0 }; #if defined(ESP8266) WiFi.macAddress(mac); - sprintf(buf, MACSTR, MAC2STR(mac)); -#else + sprintf(buf, MACSTR, MAC2STR(mac)); +#elif defined(ESP32) esp_read_mac(mac, ESP_MAC_WIFI_STA); sprintf(buf, MACSTR, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +#elif defined(LIBRETINY) + uint32_t macid = lt_cpu_get_mac_id (); + memcpy(buf, &macid, sizeof(macid)); #endif return String(buf); } \ No newline at end of file diff --git a/src/Global.cpp b/src/Global.cpp index 7f6fe4d1..f5b20197 100644 --- a/src/Global.cpp +++ b/src/Global.cpp @@ -4,7 +4,8 @@ *****************************************глобальные объекты классов*************************************************** **********************************************************************************************************************/ -TickerScheduler ts(END + 1); +// TickerScheduler ts(END + 1); // зачем на 1 больше? +TickerScheduler ts(END); WiFiClient espClient; PubSubClient mqtt(espClient); @@ -20,6 +21,9 @@ ESP8266WebServer HTTP(80); #ifdef ESP32 WebServer HTTP(80); #endif +#ifdef LIBRETINY +WebServer HTTP(80); +#endif #endif #ifdef STANDARD_WEB_SOCKETS @@ -30,11 +34,15 @@ WebSocketsServer standWebSocket = WebSocketsServer(81); ***********************************************глобальные переменные************************************************** **********************************************************************************************************************/ IoTGpio IoTgpio(0); +#ifdef mod_RtcDriver IoTItem* rtcItem = nullptr; +#endif //IoTItem* camItem = nullptr; IoTItem* tlgrmItem = nullptr; IoTBench* benchTaskItem = nullptr; IoTBench* benchLoadItem = nullptr; +IoTDiscovery* HOMEdDiscovery = nullptr; +IoTDiscovery* HADiscovery = nullptr; String settingsFlashJson = "{}"; // переменная в которой хранятся все настройки, находится в оперативной памяти и синхронизированна с flash памятью String valuesFlashJson = "{}"; // переменная в которой хранятся все значения элементов, которые необходимо сохранить на flash. Находится в оперативной памяти и синхронизированна с flash памятью String errorsHeapJson = "{}"; // переменная в которой хранятся все ошибки, находится в оперативной памяти только @@ -57,6 +65,7 @@ int mqttPort = 0; String mqttPrefix = ""; String mqttUser = ""; String mqttPass = ""; +String nameId = ""; unsigned long mqttUptime = 0; unsigned long flashWriteNumber = 0; @@ -78,7 +87,7 @@ String prevDate = ""; bool firstTimeInit = true; // unsigned long loopPeriod; - +int8_t ws_clients[WEBSOCKETS_CLIENT_MAX]; bool isTimeSynch = false; Time_t _time_local; Time_t _time_utc; diff --git a/src/Main.cpp b/src/Main.cpp index 7f338d53..d512c7c5 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -3,7 +3,13 @@ #include "classes/IoTDB.h" #include "utils/Statistic.h" #include "classes/IoTBench.h" +#ifndef LIBRETINY #include +#endif +#include "DebugTrace.h" +#if defined(ESP32) +#include +#endif #if defined(esp32s2_4mb) || defined(esp32s3_16mb) #include #endif @@ -46,7 +52,7 @@ void elementsLoop() { #define COUNTER_ERRORMARKER 4 // количество шагов счетчика #define STEPPER_ERRORMARKER 100000 // размер шага счетчика интервала доверия выполнения блока кода мкс -#if defined(esp32_4mb) || defined(esp32_16mb) || defined(esp32cam_4mb) +#if defined(esp32_4mb) || defined(esp32_4mb3f) || defined(esp32_16mb) || defined(esp32cam_4mb) static int IRAM_ATTR initErrorMarkerId = 0; // ИД маркера static int IRAM_ATTR errorMarkerId = 0; @@ -65,14 +71,14 @@ void IRAM_ATTR onTimer() { #endif void initErrorMarker(int id) { -#if defined(esp32_4mb) || defined(esp32_16mb) || defined(esp32cam_4mb) +#if defined(esp32_4mb) || defined(esp32_4mb3f) || defined(esp32_16mb) || defined(esp32cam_4mb) initErrorMarkerId = id; errorMarkerCounter = 0; #endif } void stopErrorMarker(int id) { -#if defined(esp32_4mb) || defined(esp32_16mb) || defined(esp32cam_4mb) +#if defined(esp32_4mb) || defined(esp32_4mb3f) || defined(esp32_16mb) || defined(esp32cam_4mb) errorMarkerCounter = -1; if (errorMarkerId) SerialPrint("I", "WARNING!", "A lazy (freezing loop more than " + (String)(COUNTER_ERRORMARKER * STEPPER_ERRORMARKER / 1000) + " ms) section has been found! With ID=" + (String)errorMarkerId); @@ -85,7 +91,7 @@ void setup() { #if defined(esp32s2_4mb) || defined(esp32s3_16mb) USB.begin(); #endif -#if defined(esp32_4mb) || defined(esp32_16mb) || defined(esp32cam_4mb) +#if defined(esp32_4mb) || defined(esp32_4mb3f) || defined(esp32_16mb) || defined(esp32cam_4mb) My_timer = timerBegin(0, 80, true); timerAttachInterrupt(My_timer, &onTimer, true); timerAlarmWrite(My_timer, STEPPER_ERRORMARKER, true); @@ -97,6 +103,14 @@ void setup() { Serial.begin(115200); Serial.flush(); + //----------- Отладка EXCEPTION (функции с заглушками для отключения) --------- +#if defined(RESTART_DEBUG_INFO) + //Привязка коллбэк функции для вызова при перезагрузке + esp_register_shutdown_handler(debugUpdate); +#endif // RESTART_DEBUG_INFO + // Печать или оправка отладочной информации + printDebugTrace(); + startWatchDog(); Serial.println(); Serial.println(F("--------------started----------------")); @@ -117,6 +131,8 @@ void setup() { // получение chip id setChipId(); + verifyFirmware(); + // синхронизация глобальных переменных с flash globalVarsSync(); @@ -130,17 +146,43 @@ void setup() { jsonRead(settingsFlashJson, "pinSDA", pinSDA, false); jsonRead(settingsFlashJson, "i2cFreq", i2cFreq, false); jsonRead(settingsFlashJson, "i2c", i2c, false); + //jsonWriteStr_(ssidListHeapJson, "0", "Scaning..."); if (i2c != 0) { #ifdef ESP32 Wire.end(); Wire.begin(pinSDA, pinSCL, (uint32_t)i2cFreq); -#else +#elif defined(ESP8266) Wire.begin(pinSDA, pinSCL); Wire.setClock(i2cFreq); #endif SerialPrint("i", "i2c", F("i2c pins overriding done")); } - +#if defined(RESTART_DEBUG_INFO) + esp_reset_reason_t esp_reason = esp_reset_reason(); + if (esp_reason == ESP_RST_UNKNOWN || esp_reason == ESP_RST_POWERON) + bootloop_panic_count = 0; +/* else if (bootloop_panic_count == 3 || bootloop_panic_count == 0 ) bootloop_panic_count = 1; + else if (bootloop_panic_count == 2) bootloop_panic_count = 3; + else if (bootloop_panic_count == 1) bootloop_panic_count = 2; + else bootloop_panic_count = 0; */ + Serial.println("bootloop_panic_count " + String(bootloop_panic_count)); + if (bootloop_panic_count >3 ) + { + //resetSettingsFlashByPanic(); + bootloop_panic_count = 0; + } + if (bootloop_panic_count >= 3) + { + resetSettingsFlashByPanic(); + bootloop_panic_count = -1; + } + if (bootloop_panic_count == -1) + { + SerialPrint("E", "CORE", F("CONFIG and SCENARIO reset !!!")); + bootloop_panic_count = 0; + ESP.restart(); + } +#endif // RESTART_DEBUG_INFO // настраиваем микроконтроллер configure("/config.json"); @@ -159,8 +201,11 @@ void setup() { initErrorMarker(SETUPINET_ERRORMARKER); // подключаемся к роутеру +#ifndef WIFI_ASYNC routerConnect(); - +#else + WiFiUtilsItit(); +#endif // инициализация асинхронного веб сервера и веб сокетов #ifdef ASYNC_WEB_SERVER asyncWebServerInit(); @@ -179,6 +224,10 @@ void setup() { stopErrorMarker(SETUPINET_ERRORMARKER); + // bool postMsgTelegram; + // if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1; + // sendDebugTraceAndFreeMemory(postMsgTelegram); + initErrorMarker(SETUPLAST_ERRORMARKER); elementsLoop(); @@ -188,11 +237,15 @@ void setup() { // инициализация задач переодического выполнения periodicTasksInit(); +#if !defined(WIFI_ASYNC) + // Перенесли после получения IP, так как теперь работа WiFi асинхронная // запуск работы udp addThisDeviceToList(); + #ifdef UDP_ENABLED udpListningInit(); udpBroadcastInit(); - + #endif +#endif // создаем событие завершения конфигурирования для возможности выполнения блока кода при загрузке createItemFromNet("onStart", "1", 1); @@ -201,6 +254,13 @@ void setup() { // настраиваем секундные обслуживания системы ts.add( TIMES, 1000, [&](void *) { + // сброс WDT +#if defined(ESP32) + //SerialPrint("i", "Task", "reset wdt"); + #if !defined(esp32c6_4mb) && !defined(esp32c6_8mb) //TODO esp32-c6 переписать esp_task_wdt_init + esp_task_wdt_reset(); + #endif +#endif // сохраняем значения IoTItems в файл каждую секунду, если были изменения (установлены маркеры на сохранение) if (needSaveValues) { syncValuesFlashJson(); @@ -216,14 +276,50 @@ void setup() { }, nullptr, true); + // ловим пинги от WS (2сек) и дисконнектим если их нет 3 раза 3сек*2прохода = 6сек + ts.add( + PiWS, 3000, [&](void*) { + if (isNetworkActive()) { + for (size_t i = 0; i < WEBSOCKETS_CLIENT_MAX; i++) + { + if (ws_clients[i] == 0) { + disconnectWSClient(i); + ws_clients[i]=-1; + } + if (ws_clients[i] > 0) { + ws_clients[i]=0; + } + + } + } + }, + nullptr, true); + // test - Serial.println("-------test start--------"); - Serial.println("--------test end---------"); + //Serial.println("-------test start--------"); + //Serial.println("--------test end---------"); stopErrorMarker(SETUPLAST_ERRORMARKER); +#if defined(RESTART_DEBUG_INFO) + bootloop_panic_count = 0; +#endif // RESTART_DEBUG_INFO } void loop() { +#if defined(WIFI_ASYNC) + static bool udpFirstFlag = true; + // Перенесли после получения IP, так как теперь работа WiFi асинхронная + if (isNetworkActive() && udpFirstFlag) { + udpFirstFlag = false; + // запуск работы udp + addThisDeviceToList(); + #ifdef UDP_ENABLED + udpListningInit(); + udpBroadcastInit(); + #endif + } +#endif + #ifdef LOOP_DEBUG unsigned long st = millis(); #endif diff --git a/src/MqttClient.cpp b/src/MqttClient.cpp index 727473ad..199aef48 100644 --- a/src/MqttClient.cpp +++ b/src/MqttClient.cpp @@ -1,4 +1,5 @@ #include "MqttClient.h" +#include "classes/IoTDiscovery.h" void mqttInit() { mqtt.setCallback(mqttCallback); @@ -59,10 +60,24 @@ boolean mqttConnect() { if (!mqtt.connected()) { bool connected = false; if (mqttUser != "" && mqttPass != "") { - connected = mqtt.connect(chipId.c_str(), mqttUser.c_str(), mqttPass.c_str()); + if (HOMEdDiscovery) + { + connected = mqtt.connect(chipId.c_str(), mqttUser.c_str(), mqttPass.c_str(), (HOMEdDiscovery->HOMEdTopic + "/device/custom/" + nameId).c_str(), 1, true, "{\"status\":\"offline\"}"); + } + else + { + connected = mqtt.connect(chipId.c_str(), mqttUser.c_str(), mqttPass.c_str(), (mqttRootDevice + "/state").c_str(), 1, true, "{\"status\":\"offline\"}"); + } SerialPrint("i", F("MQTT"), F("Go to connection with login and password")); } else if (mqttUser == "" && mqttPass == "") { - connected = mqtt.connect(chipId.c_str()); + if (HOMEdDiscovery) + { + connected = mqtt.connect(chipId.c_str(), (HOMEdDiscovery->HOMEdTopic + "/device/custom/" + nameId).c_str(), 1, true, "{\"status\":\"offline\"}"); + } + else + { + connected = mqtt.connect(chipId.c_str(), (mqttRootDevice + "/state").c_str(), 1, true, "{\"status\":\"offline\"}"); + } SerialPrint("i", F("MQTT"), F("Go to connection without login and password")); } else { SerialPrint("E", F("MQTT"), F("✖ Login or password missed")); @@ -105,6 +120,9 @@ void getMqttData() { mqttUser = jsonReadStr(settingsFlashJson, F("mqttUser")); mqttPass = jsonReadStr(settingsFlashJson, F("mqttPass")); mqttPrefix = jsonReadStr(settingsFlashJson, F("mqttPrefix")); + if (jsonReadInt(settingsFlashJson, F("HOMEd_names"))){ + nameId = jsonReadStr(settingsFlashJson, F("name"));} + else{nameId = getChipId();} mqttRootDevice = mqttPrefix + "/" + chipId; } @@ -129,17 +147,33 @@ void mqttSubscribe() { } } } + if(HOMEdDiscovery) + HOMEdDiscovery->mqttSubscribeDiscovery(); + if(HADiscovery) + HADiscovery->mqttSubscribeDiscovery(); + // оттправляем все статусы + if(HOMEdDiscovery || HADiscovery) + { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->iAmLocal) + { + publishStatusMqtt((*it)->getID(), (*it)->getValue()); + (*it)->onMqttWsAppConnectEvent(); + } + } + } } void mqttSubscribeExternal(String topic, bool usePrefix) { - // SerialPrint("i", F("MQTT"), mqttRootDevice); - String _sb_topic = topic; - if (usePrefix) - { - _sb_topic = mqttPrefix + "/" + topic; - } - mqtt.subscribe(_sb_topic.c_str()); + // SerialPrint("i", F("MQTT"), mqttRootDevice); + String _sb_topic = topic; + if (usePrefix) + { + _sb_topic = mqttPrefix + "/" + topic; + } + mqtt.subscribe(_sb_topic.c_str()); SerialPrint("i", F("MQTT"), ("subscribed external " + _sb_topic).c_str()); } @@ -253,6 +287,10 @@ boolean publishChartMqtt(const String& topic, const String& data) { } boolean publishStatusMqtt(const String& topic, const String& data) { + if (HOMEdDiscovery) + { + HOMEdDiscovery->publishStatusHOMEd(topic, data); + } String path = mqttRootDevice + "/" + topic + "/status"; String json = "{}"; jsonWriteStr(json, "status", data); @@ -305,14 +343,17 @@ bool publishChartFileToMqtt(String path, String id, int maxCount) { return true; } -void handleMqttStatus(bool send) { - String stateStr = getStateStr(mqtt.state()); - // Serial.println(stateStr); - jsonWriteStr_(errorsHeapJson, F("mqtt"), stateStr); - if (!send) sendStringToWs("errors", errorsHeapJson, -1); -} +// void handleMqttStatus(bool send) { +// String stateStr = getStateStr(mqtt.state()); +// // Serial.println(stateStr); +// jsonWriteStr_(errorsHeapJson, F("mqtt"), stateStr); +// if (!send) sendStringToWs("errors", errorsHeapJson, -1); +// } void handleMqttStatus(bool send, int state) { + if (state == -1) { + state = mqtt.state(); + } String stateStr = getStateStr(state); // Serial.println(stateStr); jsonWriteStr_(errorsHeapJson, F("mqtt"), stateStr); @@ -322,47 +363,47 @@ void handleMqttStatus(bool send, int state) { const String getStateStr(int e) { switch (e) { case -4: // Нет ответа от сервера - return F("e1"); - break; + return F("e1"); + break; case -3: // Соединение было разорвано - return F("e2"); - break; + return F("e2"); + break; case -2: // Ошибка соединения. Обычно возникает когда неверно указано название сервера MQTT - return F("e3"); - break; + return F("e3"); + break; case -1: // Клиент был отключен - return F("e4"); - break; + return F("e4"); + break; case 0: // подключено - return F("e5"); - break; + return F("e5"); + break; case 1: // Ошибка версии - return F("e6"); - break; + return F("e6"); + break; case 2: // Отклонен идентификатор - return F("e7"); - break; + return F("e7"); + break; case 3: // Не могу установить соединение - return F("e8"); - break; + return F("e8"); + break; case 4: // Неправильное имя пользователя/пароль - return F("e9"); - break; + return F("e9"); + break; case 5: // Не авторизован для подключения - return F("e10"); - break; + return F("e10"); + break; case 6: // Название сервера пустое - return F("e11"); - break; + return F("e11"); + break; case 7: // Имя пользователя или пароль пустые - return F("e12"); - break; + return F("e12"); + break; case 8: // Подключение в процессе - return F("e13"); - break; - default: - return F("unk"); - break; + return F("e13"); + break; + default: + return F("unk"); + break; } } diff --git a/src/NTP.cpp b/src/NTP.cpp index ba0cbe37..546e10eb 100644 --- a/src/NTP.cpp +++ b/src/NTP.cpp @@ -1,14 +1,29 @@ #include "NTP.h" +#if defined(LIBRETINY) +#include "lwip/apps/sntp.h" +#endif + #include "Global.h" #include "utils/SerialPrint.h" void ntpInit() { +#if defined(LIBRETINY) + if (sntp_enabled()) { + sntp_stop(); + } + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, jsonReadStr(settingsFlashJson, F("ntp")).c_str()); + sntp_setservername(1, "pool.ntp.org"); + sntp_setservername(2, "ru.pool.ntp.org"); + sntp_init(); +#endif synchTime(); ts.add( TIME, 1000, [&](void*) { unixTime = getSystemTime(); + //SerialPrint("I", F("NTP"), "TIME " + String(unixTime)); if (unixTime < MIN_DATETIME) { isTimeSynch = false; // SerialPrint("E", "NTP", "Time not synched"); @@ -16,9 +31,13 @@ void ntpInit() { synchTime(); // проверяем присутствие RTC с батарейкой и получаем время при наличии +#ifdef mod_RtcDriver if (rtcItem) { unixTime = rtcItem->getRtcUnixTime(); - } else return; + } + else +#endif + return; // ToDo разобраться: если нет железных часов дальше ничего не делать ??? } unixTimeShort = unixTime - START_DATETIME; @@ -44,7 +63,20 @@ void ntpInit() { } void synchTime() { +#if defined LIBRETINY + // force resync + if (sntp_enabled()) { + sntp_stop(); + } + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, jsonReadStr(settingsFlashJson, F("ntp")).c_str()); + sntp_setservername(1, "pool.ntp.org"); + sntp_setservername(2, "ru.pool.ntp.org"); + sntp_init(); + +#else configTime(0, 0, "pool.ntp.org", "ru.pool.ntp.org", jsonReadStr(settingsFlashJson, F("ntp")).c_str()); +#endif } //событие смены даты diff --git a/src/PeriodicTasks.cpp b/src/PeriodicTasks.cpp index f9fa9c9d..4455d088 100644 --- a/src/PeriodicTasks.cpp +++ b/src/PeriodicTasks.cpp @@ -59,9 +59,29 @@ String ESP_getResetReason(void) { return ESP.getResetReason(); } #endif -#if defined(esp32s2_4mb) || defined(esp32s3_16mb) || defined(esp32c3m_4mb) +#ifdef LIBRETINY String ESP_getResetReason(void) { - return ESP32GetResetReason(0); // CPU 0 + return ESP.getResetReason(); +} +#endif +#if defined(esp32s2_4mb) || defined(esp32s3_16mb) || defined(esp32c3m_4mb) || defined(esp32c6_4mb) || defined(esp32c6_8mb) +String ESP_getResetReason(void) { + // return ESP32GetResetReason(0); // CPU 0 + esp_reset_reason_t esp_reason = esp_reset_reason(); + switch (esp_reason) { + case ESP_RST_UNKNOWN: return "UNKNOWN"; + case ESP_RST_POWERON: return "POWER ON"; + case ESP_RST_EXT: return "EXTERNAL PIN"; + case ESP_RST_SW: return "SOFTWARE RESET"; + case ESP_RST_PANIC: return "EXCEPTION / PANIC"; + case ESP_RST_INT_WDT: return "INTERRUPT WATCHDOG"; + case ESP_RST_TASK_WDT: return "TASK WATCHDOG"; + case ESP_RST_WDT: return "WATCHDOGS"; + case ESP_RST_DEEPSLEEP: return "EXITING DEEP SLLEP MODE"; + case ESP_RST_BROWNOUT: return "BROWNOUT"; + case ESP_RST_SDIO: return "SDIO"; + default : return "NO MEAN"; + }; } String ESP32GetResetReason(uint32_t cpu_no) { // tools\sdk\include\esp32\rom\rtc.h @@ -82,8 +102,8 @@ String ESP32GetResetReason(uint32_t cpu_no) { return F("Timer Group1 Watchdog reset digital core"); // 8 case RTCWDT_SYS_RESET: return F("RTC Watchdog Reset digital core"); // 9 - case INTRUSION_RESET: - return F("Instrusion tested to reset CPU"); // 10 +// case INTRUSION_RESET: +// return F("Instrusion tested to reset CPU"); // 10 case TG0WDT_CPU_RESET: return F("Time Group reset CPU"); // 11 case RTC_SW_CPU_RESET: @@ -101,9 +121,24 @@ String ESP32GetResetReason(uint32_t cpu_no) { } } #endif -#if defined(esp32_4mb) || defined(esp32_16mb) || defined(esp32cam_4mb) +#if defined(esp32_4mb) || defined(esp32_4mb3f) || defined(esp32_16mb) || defined(esp32cam_4mb) String ESP_getResetReason(void) { - return ESP32GetResetReason(0); // CPU 0 + // return ESP32GetResetReason(0); // CPU 0 + esp_reset_reason_t esp_reason = esp_reset_reason(); + switch (esp_reason) { + case ESP_RST_UNKNOWN: return "UNKNOWN"; + case ESP_RST_POWERON: return "POWER ON"; + case ESP_RST_EXT: return "EXTERNAL PIN"; + case ESP_RST_SW: return "SOFTWARE RESET"; + case ESP_RST_PANIC: return "EXCEPTION / PANIC"; + case ESP_RST_INT_WDT: return "INTERRUPT WATCHDOG"; + case ESP_RST_TASK_WDT: return "TASK WATCHDOG"; + case ESP_RST_WDT: return "WATCHDOGS"; + case ESP_RST_DEEPSLEEP: return "EXITING DEEP SLLEP MODE"; + case ESP_RST_BROWNOUT: return "BROWNOUT"; + case ESP_RST_SDIO: return "SDIO"; + default : return "NO MEAN"; + }; } String ESP32GetResetReason(uint32_t cpu_no) { // tools\sdk\include\esp32\rom\rtc.h diff --git a/src/StandWebServer.cpp b/src/StandWebServer.cpp index 6a23378f..4ff4c90c 100644 --- a/src/StandWebServer.cpp +++ b/src/StandWebServer.cpp @@ -9,6 +9,12 @@ static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR"; static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound"; // static bool fsOK; // const char* fsName = "LittleFS"; +// Типы обновлений +enum UpdateType { + FIRMWARE, + FILESYSTEM + }; + void standWebServerInit() { // Кэшировать файлы для быстрой работы @@ -88,6 +94,17 @@ void standWebServerInit() { // - first callback is called after the request has ended with all parsed arguments // - second callback handles file upload at that location HTTP.on("/edit", HTTP_POST, replyOK, handleFileUpload); + // отображение страницы с полем ввода для сервера обновления + HTTP.on("/localota", HTTP_GET, handleLocalOTA); + // непосредственно ОТА обновление со стороннего сервера + HTTP.on("/localota_handler", HTTP_GET, handleLocalOTA_Handler); + + // Обработка обновления от WS drag&drop + HTTP.on("/update", HTTP_POST, []() { + HTTP.send(200); // Для CORS + }, handleUpdateOTA); + + HTTP.on("/update", HTTP_OPTIONS, handleCors); // Default handler for all URIs not defined above // Use it to read files from filesystem @@ -156,6 +173,75 @@ void handleStatus() { HTTP.send(200, "application/json", json); } +void handleLocalOTA() { + String page = "
"; + HTTP.send(200, "text/html", page);} + + +void handleCors() { + HTTP.sendHeader("Access-Control-Allow-Origin", "*"); + HTTP.send(200); + } + + void handleUpdateOTA() { + UpdateType typeOTAfile = FIRMWARE; + HTTPUpload& upload = HTTP.upload(); + if (upload.filename != "firmware.bin" && upload.filename != "littlefs.bin") + { + SerialPrint("E", F("OTA"), "Неверное имя файла: " + upload.filename); + return; + } + if (upload.filename == "firmware.bin") + { + typeOTAfile = FIRMWARE; + } else if (upload.filename == "littlefs.bin") + { + typeOTAfile = FILESYSTEM; + } + #ifdef ESP8266 + size_t size = upload.totalSize; + int updatePartition = (typeOTAfile == FIRMWARE)? U_FLASH : U_FS; //U_FS + //#endif + #else //ESP32 + size_t size = UPDATE_SIZE_UNKNOWN; + int updatePartition = (typeOTAfile == FIRMWARE)? U_FLASH : U_SPIFFS; //U_FS + #endif + if (upload.status == UPLOAD_FILE_START) { + //Serial.print("Начало загрузки: "); + //Serial.println(upload.filename); + SerialPrint("i", F("OTA"), "Начало загрузки файла: " + upload.filename); + if (!Update.begin(size, updatePartition)) { // UPDATE_SIZE_UNKNOWN 0xFFFFFFFF + Update.end(); + SerialPrint("E", F("OTA"), "Ошибка: Недостаточно памяти"); + HTTP.send(500, "text/plain", "Ошибка: Недостаточно памяти"); + return; + } + } + else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Update.end(); + SerialPrint("E", F("OTA"), "Ошибка записи данных"); + HTTP.send(500, "text/plain", "Ошибка записи данных"); + return; + } + } + else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { // true - перезагрузка после обновления + SerialPrint("i", F("OTA"), "Обновление завершено"); + HTTP.send(200, "text/plain", "Обновление успешно"); + ESP.restart(); + } else { + Update.end(); + SerialPrint("E", F("OTA"), "Ошибка завершения обновления"); + HTTP.send(500, "text/plain", "Ошибка завершения обновления"); + } + } + } +void handleLocalOTA_Handler() { + String serverValue = HTTP.arg("server"); + upgrade_firmware(3,serverValue); +} + #ifdef ESP32 String getContentType(String filename) { if (HTTP.hasArg("download")) { @@ -231,7 +317,12 @@ bool handleFileRead(String path) { return the path of the closest parent still existing */ String lastExistingParent(String path) { - while (!path.isEmpty() && !FileFS.exists(path)) { + #ifndef LIBRETINY + while (!path.isEmpty() && !FileFS.exists(path)) + #else + while (!path.length()==0 && !FileFS.exists(path)) + #endif + { if (path.lastIndexOf('/') > 0) { path = path.substring(0, path.lastIndexOf('/')); } else { @@ -278,7 +369,7 @@ void handleFileUpload() { } } -#ifdef ESP8266 +#if defined ESP8266 void deleteRecursive(String path) { File file = FileFS.open(path, "r"); bool isDir = file.isDirectory(); @@ -299,14 +390,14 @@ void deleteRecursive(String path) { } #endif -#ifdef ESP32 +#if defined ESP32 || defined LIBRETINY struct treename { uint8_t type; char *name; }; void deleteRecursive(String path) { - fs::File dir = FileFS.open(path); + fs::File dir = FileFS.open(path.c_str()); if (!dir.isDirectory()) { Serial.printf("%s is a file\n", path); @@ -321,7 +412,11 @@ void deleteRecursive(String path) { while (entry = dir.openNextFile()) { if (entry.isDirectory()) { +#if defined ESP32 deleteRecursive(entry.path()); +#elif defined LIBRETINY + deleteRecursive(entry.fullName()); +#endif } else { String tmpname = path + "/" + strdup(entry.name()); // buffer file name entry.close(); @@ -342,10 +437,15 @@ void deleteRecursive(String path) { */ void handleFileDelete() { String path = HTTP.arg(0); + #ifndef LIBRETINY if (path.isEmpty() || path == "/") { return replyBadRequest("BAD PATH"); } - + #else + if (path.length()==0 || path == "/") { + return replyBadRequest("BAD PATH"); + } + #endif // DBG_OUTPUT_PORT.println(String("handleFileDelete: ") + path); if (!FileFS.exists(path)) { return replyNotFound(FPSTR(FILE_NOT_FOUND)); @@ -368,10 +468,15 @@ void handleFileDelete() { */ void handleFileCreate() { String path = HTTP.arg("path"); + #ifndef LIBRETINY if (path.isEmpty()) { return replyBadRequest(F("PATH ARG MISSING")); } - + #else + if (path.length()==0) { + return replyBadRequest(F("PATH ARG MISSING")); + } + #endif #ifdef USE_SPIFFS if (checkForUnsupportedPath(path).length() > 0) { return replyServerError(F("INVALID FILENAME")); @@ -386,7 +491,11 @@ void handleFileCreate() { } String src = HTTP.arg("src"); + #ifndef LIBRETINY if (src.isEmpty()) { + #else + if (src.length()==0) { + #endif // No source specified: creation // DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path); if (path.endsWith("/")) { @@ -404,6 +513,9 @@ void handleFileCreate() { #endif #ifdef ESP32 file.write(0); +#endif +#ifdef LIBRETINY + file.write((uint8_t)0); #endif file.close(); } else { @@ -509,7 +621,7 @@ void handleFileList() { } #endif -#ifdef ESP32 +#if defined ESP32 void handleFileList() { if (!HTTP.hasArg("dir")) { HTTP.send(500, "text/plain", "BAD ARGS"); @@ -517,15 +629,29 @@ void handleFileList() { } String path = HTTP.arg("dir"); - // DBG_OUTPUT_PORT.println("handleFileList: " + path); - + if (path != "/" && !FileFS.exists(path)) { + return replyBadRequest("BAD PATH"); + } + //path = "/build/"; + Serial.println("handleFileList: " + path); +#if defined LIBRETINY + File root = FileFS.open(path.c_str()); + //Dir root = FileFS.openDir(path); + Serial.println("handleFileList FIRST OPEN Name: " + String(root.name())); +#else File root = FileFS.open(path); +#endif path = String(); String output = "["; if (root.isDirectory()) { + Serial.println("handleFileList IS DIR: " + String(root.name())); + //root.close(); File file = root.openNextFile(); + Serial.println("handleFileList openNextFile: " + String(file.name())); + while (file) { + if (output != "[") { output += ','; } @@ -549,6 +675,67 @@ void handleFileList() { } #endif +#if defined LIBRETINY +void handleFileList() { + if (!HTTP.hasArg("dir")) { + HTTP.send(500, "text/plain", "BAD ARGS"); + return; + } + String path = HTTP.arg("dir"); + if (path != "/" && !FileFS.exists(path)) { + return replyBadRequest("BAD PATH"); + } +FileFS.open(path.c_str()); + lfs_dir_t dir; + struct lfs_info info; + int err = lfs_dir_open(FileFS.getFS(), &dir, path.c_str()); + if (err) { + HTTP.send(500, "text/plain", "FAIL OPEN DIR"); + return; + } + String output = "["; + while (true) { + int res = lfs_dir_read(FileFS.getFS(), &dir, &info); + if (res < 0) { + lfs_dir_close(FileFS.getFS(), &dir); + return ; + } + + if (!res) { + break; + } + + Serial.printf("%s %d", info.name, info.type); + + if (output != "[") { + output += ','; + } + output += "{\"type\":\""; + // output += (file.isDirectory()) ? "dir" : "file"; + if (info.type == LFS_TYPE_DIR) { + output += "dir"; + } else { + output += F("file\",\"size\":\""); + output += info.size; + } + + output += "\",\"name\":\""; + output += String(info.name); + output += "\"}"; + //file = root.openNextFile(); + + } + + err = lfs_dir_close(FileFS.getFS(), &dir); + if (err) { + HTTP.send(500, "text/plain", "FAIL CLOSE DIR"); + return; + } + + output += "]"; + HTTP.send(200, "text/json", output); +} +#endif /* The "Not Found" handler catches all URI not explicitly declared in code First try to find and return the requested file from the filesystem, @@ -560,6 +747,9 @@ void handleNotFound() { #endif #ifdef ESP32 String uri = WebServer::urlDecode(HTTP.uri()); // required to read paths with blanks +#endif +#ifdef LIBRETINY + String uri = WebServer::urlDecode(HTTP.uri()); // required to read paths with blanks #endif if (handleFileRead(uri)) { return; diff --git a/src/UpgradeFirm.cpp b/src/UpgradeFirm.cpp index 525ea345..6b3baa30 100644 --- a/src/UpgradeFirm.cpp +++ b/src/UpgradeFirm.cpp @@ -3,6 +3,9 @@ updateFirm update; void upgrade_firmware(int type, String path) { + if (path == ""){ + path = getBinPath(); + } putUserDataToRam(); // сбросим файл статуса последнего обновления writeFile("ota.json", "{}"); @@ -36,6 +39,7 @@ void upgrade_firmware(int type, String path) { bool upgradeFS(String path) { bool ret = false; +#ifndef LIBRETINY WiFiClient wifiClient; SerialPrint("!!!", F("Update"), "Start upgrade FS... " + path); @@ -43,7 +47,7 @@ bool upgradeFS(String path) { SerialPrint("E", F("Update"), F("FS Path error")); saveUpdeteStatus("fs", PATH_ERROR); return ret; - } + } #ifdef ESP8266 ESPhttpUpdate.rebootOnUpdate(false); t_httpUpdate_return retFS = ESPhttpUpdate.updateFS(wifiClient, path + "/littlefs.bin"); @@ -57,21 +61,27 @@ bool upgradeFS(String path) { // если FS обновилась успешно if (retFS == HTTP_UPDATE_OK) { SerialPrint("!!!", F("Update"), F("FS upgrade done!")); + //HTTP.send(200, "text/plain", "FS upgrade done!"); saveUpdeteStatus("fs", UPDATE_COMPLETED); ret = true; } else { saveUpdeteStatus("fs", UPDATE_FAILED); if (retFS == HTTP_UPDATE_FAILED) { SerialPrint("E", F("Update"), "HTTP_UPDATE_FAILED"); + String page = "Ошибка обновления FS!
FS Update failed!
Home"; + HTTP.send(200, "text/html; charset=UTF-8", page); } else if (retFS == HTTP_UPDATE_NO_UPDATES) { - SerialPrint("E", F("Update"), "HTTP_UPDATE_NO_UPDATES"); + SerialPrint("E", F("Update"), "HTTP_UPDATE_NO_UPDATES! DELETE /localota !!!"); + //HTTP.send(200, "text/plain", "NO_UPDATES"); } } +#endif return ret; } bool upgradeBuild(String path) { bool ret = false; +#ifndef LIBRETINY WiFiClient wifiClient; SerialPrint("!!!", F("Update"), "Start upgrade BUILD... " + path); @@ -79,7 +89,7 @@ bool upgradeBuild(String path) { SerialPrint("E", F("Update"), F("Build Path error")); saveUpdeteStatus("build", PATH_ERROR); return ret; - } + } #if defined(esp8266_4mb) || defined(esp8266_16mb) || defined(esp8266_1mb) || defined(esp8266_1mb_ota) || defined(esp8266_2mb) || defined(esp8266_2mb_ota) ESPhttpUpdate.rebootOnUpdate(false); t_httpUpdate_return retBuild = ESPhttpUpdate.update(wifiClient, path + "/firmware.bin"); @@ -92,16 +102,23 @@ bool upgradeBuild(String path) { // если BUILD обновился успешно if (retBuild == HTTP_UPDATE_OK) { SerialPrint("!!!", F("Update"), F("BUILD upgrade done!")); + String page = "Обновление BUILD выполнено!
Build upgrade done!
Home"; + HTTP.send(200, "text/html; charset=UTF-8", page); saveUpdeteStatus("build", UPDATE_COMPLETED); ret = true; } else { saveUpdeteStatus("build", UPDATE_FAILED); if (retBuild == HTTP_UPDATE_FAILED) { SerialPrint("E", F("Update"), "HTTP_UPDATE_FAILED"); + String page = "Ошибка обновления прошивки!
Firmware update failed!
Home"; + HTTP.send(200, "text/html; charset=UTF-8", page); } else if (retBuild == HTTP_UPDATE_NO_UPDATES) { SerialPrint("E", F("Update"), "HTTP_UPDATE_NO_UPDATES"); + String page = "Нет обновлений!
No updates!
Home"; + HTTP.send(200, "text/html; charset=UTF-8", page); } } +#endif return ret; } @@ -111,23 +128,37 @@ void restartEsp() { ESP.restart(); } -// теперь путь к обнавленю прошивки мы получаем из веб интерфейса -// const String getBinPath(String file) { -// String path = "error"; -// int targetVersion = 0; -// String serverip; -// if (jsonRead(errorsHeapJson, F("chver"), targetVersion)) { -// if (targetVersion >= 400) { -// if (jsonRead(settingsFlashJson, F("serverip"), serverip)) { -// if (serverip != "") { -// path = jsonReadStr(settingsFlashJson, F("serverip")) + "/iotm/" + String(FIRMWARE_NAME) + "/" + String(targetVersion) + "/" + file; -// } -// } -// } -// } -// SerialPrint("i", F("Update"), "path: " + path); -// return path; -// } +//теперь путь к обнавленю прошивки мы получаем из веб интерфейса +const String getBinPath() { + String path = "error"; + int targetVersion = 400; //HACKFUCK local OTA version in PrepareServer.py + String serverip; + if (jsonRead(errorsHeapJson, F("chver"), targetVersion)) { + if (targetVersion >= 400) + { + if (jsonRead(settingsFlashJson, F("serverip"), serverip)) + { + if (serverip != "") + { + path = jsonReadStr(settingsFlashJson, F("serverip")) + "/iotm/" + String(FIRMWARE_NAME) + "/" + String(targetVersion) + "/" ; + } + } + } + } + else if (targetVersion >= 400) + { + if (jsonRead(settingsFlashJson, F("serverlocal"), serverip)) { + if (serverip != "") + { + path = jsonReadStr(settingsFlashJson, F("serverlocal")) + "/iotm/" + String(FIRMWARE_NAME) + "/" + String(targetVersion) + "/"; + } + } + } + + + SerialPrint("i", F("Update"), "server local: " + path); + return path; +} // https://t.me/IoTmanager/128814/164752 - убрал ограничение void putUserDataToRam() { diff --git a/src/WsServer.cpp b/src/WsServer.cpp index 98123e75..0d2b3301 100644 --- a/src/WsServer.cpp +++ b/src/WsServer.cpp @@ -7,6 +7,10 @@ void standWebSocketsInit() { standWebSocket.begin(); standWebSocket.onEvent(webSocketEvent); SerialPrint("i", "WS", "WS server initialized"); + for (size_t i = 0; i < WEBSOCKETS_CLIENT_MAX; i++) + { + ws_clients[i] = -1; + } } void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) { @@ -17,6 +21,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) case WStype_DISCONNECTED: { Serial.printf("[%u] Disconnected!\n", num); + standWebSocket.disconnect(num); } break; case WStype_CONNECTED: { @@ -54,7 +59,11 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) //----------------------------------------------------------------------// // Страница веб интерфейса dashboard //----------------------------------------------------------------------// - + if (headerStr == "/pi|") { + standWebSocket.sendTXT(num, "/po|"); + Serial.printf("Ping client: %u\n", num); + ws_clients[num]=1; + } // публикация всех виджетов if (headerStr == "/|") { sendFileToWsByFrames("/layout.json", "layout", "", num, WEB_SOCKETS_FRAME_SIZE); @@ -134,6 +143,10 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) sendFileToWsByFrames("/widgets.json", "widget", "", num, WEB_SOCKETS_FRAME_SIZE); sendFileToWsByFrames("/config.json", "config", "", num, WEB_SOCKETS_FRAME_SIZE); sendStringToWs("settin", settingsFlashJson, num); +#ifdef WIFI_ASYNC + ssidListHeapJson = "{}"; + jsonWriteStr_(ssidListHeapJson, "0", "Scanning..."); +#endif sendStringToWs("ssidli", ssidListHeapJson, num); sendStringToWs("errors", errorsHeapJson, num); // запуск асинхронного сканирования wifi сетей при переходе на страницу @@ -148,6 +161,12 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) sendStringToWs("errors", errorsHeapJson, num); // если не было создано приема данных по udp - то создадим его addThisDeviceToList(); +#ifdef WIFI_ASYNC + settingsFlashJson = readFile(F("settings.json"), 4096); + settingsFlashJson.replace("\r\n", ""); + Serial.println(settingsFlashJson); + WiFiUtilsItit(); +#endif } // обработка кнопки сохранить настройки mqtt @@ -164,10 +183,21 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) // запуск асинхронного сканирования wifi сетей при нажатии выпадающего // списка if (headerStr == "/scan|") { +#ifndef WIFI_ASYNC std::vector jArray; jsonReadArray(settingsFlashJson, "routerssid", jArray); RouterFind(jArray); sendStringToWs("ssidli", ssidListHeapJson, num); +#else + //String ssidScan = "{Scaning...}"; + //ssidListHeapJson = "{}"; + //jsonWriteStr_(ssidListHeapJson, "0", "Scanning..."); + //Serial.println("Async scan:" + String(ssidListHeapJson)); + sendStringToWs("ssidli", ssidListHeapJson, num); + if (ssidListHeapJson == "{\"0\":\"Scanning...\"}") + ScanAsync(); +#endif + } //----------------------------------------------------------------------// @@ -203,6 +233,50 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) sendStringToWs("settin", settingsFlashJson, num); } + if (headerStr == "/localt|") { + String timeStr = String((char*)payload + 8); + //Serial.println("Время с фронта: /localt|" + timeStr); + + // Обрезаем дробную часть, если есть + int dotIndex = timeStr.indexOf('.'); + if (dotIndex != -1) { + timeStr = timeStr.substring(0, dotIndex); + } + + // Парсим UNIX-время в секундах + time_t unixTime = (time_t)timeStr.toInt(); + + // Создаём структуру timeval + timeval tv; + tv.tv_sec = unixTime; // Секунды эпохи + tv.tv_usec = 0; // Микросекунды + + // Устанавливаем время + if (settimeofday(&tv, NULL) == 0) { + //Serial.printf("Время установлено: %ld\n", unixTime); + #ifdef LIBRETINY + SerialPrint("i", F("Time"), "Время установлено из браузера: "); + #else + SerialPrint("i", F("Time"), "Время установлено из браузера: " + String(unixTime)); + #endif + } else { + #ifdef LIBRETINY + //Serial.printf("Ошибка установки времени: %ld\n", unixTime); + SerialPrint("i", F("=>WS"), "Ошибка установки времени: "); + #else + SerialPrint("i", F("=>WS"), "Ошибка установки времени: " + String(unixTime)); + #endif + } + // timeval tv2{0, 0}; + // timezone tz = timezone{0, 0}; + // time_t epoch = 0; + // if (gettimeofday(&tv2, &tz) != -1) { + // epoch = tv2.tv_sec; + // } + // unixTime = epoch; + // SerialPrint("I", F("NTP"), "TIME " + String(unixTime)); + } + //----------------------------------------------------------------------// // Страница веб интерфейса dev //----------------------------------------------------------------------// @@ -378,6 +452,7 @@ void sendFileToWsByFrames(const String& filename, const String& header, const St auto path = filepath(filename); auto file = FileFS.open(path, "r"); + //SerialPrint("I", "sendFileToWsByFrames", ("reed file: ")+ path); if (!file) { SerialPrint("E", "FS", F("reed file error")); return; @@ -425,16 +500,25 @@ void sendFileToWsByFrames(const String& filename, const String& header, const St continuation = true; } - // Serial.println(String(i) + ") " + "ws: " + String(client_id) + " fr sz: - // " + String(size) + " fin: " + String(fin) + " cnt: " + - // String(continuation)); - +// Serial.println(String(i) + ") " + "ws: " + String(client_id) + " fr sz: " +// + String(size) + " fin: " + String(fin) + " cnt: " + +// String(continuation)); +#ifdef ASYNC_WEB_SOCKETS + if (client_id == -1) { + //ws.broadcastBIN(frameBuf, size, fin, continuation); + ws.binaryAll(frameBuf, size); + } else { + //ws.sendBIN(client_id, frameBuf, size, fin, continuation); + ws.binary(client_id,frameBuf, size); + } +#elif defined (STANDARD_WEB_SOCKETS) if (client_id == -1) { standWebSocket.broadcastBIN(frameBuf, size, fin, continuation); } else { standWebSocket.sendBIN(client_id, frameBuf, size, fin, continuation); } +#endif } i++; } @@ -444,7 +528,12 @@ void sendFileToWsByFrames(const String& filename, const String& header, const St } void sendStringToWs(const String& header, String& payload, int client_id) { - if ((!getNumAPClients() && !isNetworkActive()) || !getNumWSClients()) { +#ifdef LIBRETINY + if (/* (!getNumAPClients() && !isNetworkActive()) || */ !getNumWSClients()) { +#else + if ( (!getNumAPClients() && !isNetworkActive()) || !getNumWSClients()) { +#endif + // SerialPrint("E", "sendStringToWs", "getNumAPClients: " + String(getNumAPClients()) + "isNetworkActive: " + String(isNetworkActive() + "getNumWSClients: " + String(getNumWSClients()))); // standWebSocket.disconnect(); // это и ниже надо сделать при - // standWebSocket.close(); // - отключении AP И WiFi(STA), надо менять ядро WiFi. Сейчас не закрывается сессия клиента при пропаже AP И WiFi(STA) return; @@ -457,17 +546,32 @@ void sendStringToWs(const String& header, String& payload, int client_id) { String msg = header + "|0012|" + payload; size_t totalSize = msg.length(); - + // SerialPrint("E", "sendStringToWs", msg); char dataArray[totalSize]; msg.toCharArray(dataArray, totalSize + 1); +#ifdef ASYNC_WEB_SOCKETS + if (client_id == -1) { + ws.binaryAll((uint8_t*)dataArray, totalSize); + } else { + ws.binary(client_id, (uint8_t*)dataArray, totalSize); + } +#elif defined (STANDARD_WEB_SOCKETS) if (client_id == -1) { standWebSocket.broadcastBIN((uint8_t*)dataArray, totalSize); } else { standWebSocket.sendBIN(client_id, (uint8_t*)dataArray, totalSize); } +#endif } -void sendDeviceList(uint8_t num) { +void disconnectWSClient(uint8_t client_id) +{ + standWebSocket.disconnect(client_id); + Serial.printf("[WS] Client %u -disconnected\n", client_id); +} + +void sendDeviceList(uint8_t num) +{ if (jsonReadInt(settingsFlashJson, F("udps")) != 0) { // если включен автопоиск то отдаем список из оперативной памяти SerialPrint("i", "FS", "heap list"); @@ -478,5 +582,8 @@ void sendDeviceList(uint8_t num) { SerialPrint("i", "FS", "flash list"); } } - -int getNumWSClients() { return standWebSocket.connectedClients(false); } \ No newline at end of file +#ifdef ASYNC_WEB_SOCKETS +int getNumWSClients() { return ws.count(); } +#elif defined (STANDARD_WEB_SOCKETS) +int getNumWSClients() { return standWebSocket.connectedClients(false); } +#endif \ No newline at end of file diff --git a/src/classes/IoTDiscovery.cpp b/src/classes/IoTDiscovery.cpp new file mode 100644 index 00000000..9eb202c6 --- /dev/null +++ b/src/classes/IoTDiscovery.cpp @@ -0,0 +1,31 @@ +#include "Global.h" +#include "classes/IoTDiscovery.h" + +IoTDiscovery::IoTDiscovery(const String ¶meters) : IoTItem(parameters) +{ + /* int _tx, _rx, _speed, _line; + jsonRead(parameters, "rx", _rx); + jsonRead(parameters, "tx", _tx); + jsonRead(parameters, "speed", _speed); + jsonRead(parameters, "line", _line); + */ + //ChipId = getChipId(); +} + +void IoTDiscovery::publishStatusHOMEd(const String &topic, const String &data) {} +void IoTDiscovery::getlayoutHA() {} +void IoTDiscovery::getlayoutHOMEd() {} +void IoTDiscovery::deleteFromHOMEd() {} +void IoTDiscovery::mqttSubscribeDiscovery(){} + +boolean IoTDiscovery::publishRetain(const String &topic, const String &data) +{ + if (mqtt.beginPublish(topic.c_str(), data.length(), true)) + { + mqtt.print(data); + return mqtt.endPublish(); + } + return false; +} + +IoTDiscovery::~IoTDiscovery() {} diff --git a/src/classes/IoTGpio.cpp b/src/classes/IoTGpio.cpp index f5bef51f..b3161990 100644 --- a/src/classes/IoTGpio.cpp +++ b/src/classes/IoTGpio.cpp @@ -9,7 +9,7 @@ IoTGpio::~IoTGpio(){ } - +#ifndef LIBRETINY void IoTGpio::pinMode(int pin, uint8_t mode) { int pinH = pin/100; if (_drivers[pinH]) _drivers[pinH]->pinMode(pin - pinH*100, mode); @@ -21,7 +21,19 @@ void IoTGpio::digitalWrite(int pin, uint8_t val) { if (_drivers[pinH]) _drivers[pinH]->digitalWrite(pin - pinH*100, val); else ::digitalWrite(pin, val); } +#else +void IoTGpio::pinMode(int pin, uint8_t mode) { + int pinH = pin/100; + if (_drivers[pinH]) _drivers[pinH]->pinMode(pin - pinH*100, mode); + else ::pinMode(pin, (PinMode)mode); +} +void IoTGpio::digitalWrite(int pin, uint8_t val) { + int pinH = pin/100; + if (_drivers[pinH]) _drivers[pinH]->digitalWrite(pin - pinH*100, val); + else ::digitalWrite(pin, (PinStatus)val); +} +#endif int IoTGpio::digitalRead(int pin) { int pinH = pin/100; if (_drivers[pinH]) return _drivers[pinH]->digitalRead(pin - pinH*100); @@ -51,7 +63,11 @@ void IoTGpio::analogWrite(int pin, int val) { void IoTGpio::digitalInvert(int pin) { int pinH = pin/100; if (_drivers[pinH]) _drivers[pinH]->digitalInvert(pin - pinH*100); - else ::digitalWrite(pin, 1 - ::digitalRead(pin)); +#ifdef LIBRETINY + else ::digitalWrite(pin, (PinStatus)(1 - ::digitalRead(pin))); +#else + else ::digitalWrite(pin, (1 - ::digitalRead(pin))); +#endif } diff --git a/src/classes/IoTItem.cpp b/src/classes/IoTItem.cpp index fd078a6f..6380aa62 100644 --- a/src/classes/IoTItem.cpp +++ b/src/classes/IoTItem.cpp @@ -178,7 +178,7 @@ void IoTItem::checkIntFromNet() { if (_intFromNet >= 0) { // если время жизни истекло, то удаляем элемент чуть позже на следующем такте loop // если это было уведомление не об ошибке или начале работы, то сообщаем, что сетевое событие давно не приходило - if (_intFromNet == 0 && _id.indexOf("onError") == -1 && _id.indexOf("onStart") == -1 && _id.indexOf("onInit") == -1) { + if (_intFromNet == 0 && _id.indexOf("onError") == -1 && _id.indexOf("onStart") == -1 && _id.indexOf("onInit") == -1 && _id.indexOf("onWifi") == -1) { SerialPrint("E", _id, "The new data did not come from the network. The level of trust is low.", _id); } _intFromNet--; @@ -238,9 +238,12 @@ IoTGpio* IoTItem::getGpioDriver() { return nullptr; } +#ifdef mod_RtcDriver IoTItem* IoTItem::getRtcDriver() { return nullptr; } +#endif + /* IoTItem* IoTItem::getCAMDriver() { return nullptr; @@ -258,10 +261,20 @@ IoTBench *IoTItem::getBenchmarkLoad() { return nullptr; } +IoTDiscovery *IoTItem::getHOMEdDiscovery() +{ + return nullptr; +} +IoTDiscovery *IoTItem::getHADiscovery() +{ + return nullptr; +} +#ifdef mod_RtcDriver unsigned long IoTItem::getRtcUnixTime() { return 0; } +#endif // сетевое общение==================================================================================================================================== @@ -281,7 +294,7 @@ unsigned long IoTItem::getRtcUnixTime() //========================================================================================================================================= -IoTItem* myIoTItem; +// IoTItem* myIoTItem; // экономим память, используется в одном месте // поиск элемента модуля в существующей конфигурации IoTItem* findIoTItem(const String& name) { diff --git a/src/classes/IoTScenario.cpp b/src/classes/IoTScenario.cpp index 6aa26e77..7b7ad05a 100644 --- a/src/classes/IoTScenario.cpp +++ b/src/classes/IoTScenario.cpp @@ -2,6 +2,7 @@ #include "classes/IoTItem.h" #include "classes/IoTScenario.h" #include "utils/FileUtils.h" +#include "utils/WiFiUtils.h" #include "NTP.h" @@ -291,6 +292,11 @@ class CallExprAST : public ExprAST { ret.valD = Item->getIntFromNet(); ret.isDecimal = true; return &ret; + + } else if (Cmd == F("doByInterval")) { // вызываем системную функцию периодического выполнения вне таймера + Item->doByInterval(); + ret = Item->value; + return &ret; } // если все же все ок, то готовим параметры для передачи в модуль @@ -304,6 +310,15 @@ class CallExprAST : public ExprAST { return nullptr; // ArgsAsIoTValue.push_back(zeroIotVal); } + if (Cmd == F("setInterval")) { // меняем интервал выполнения задач модуля налету + if (ArgsAsIoTValue.size() == 1) { + Item->setInterval(ArgsAsIoTValue[0].valD); + ret.valD = Item->getInterval(); + ret.isDecimal = true; + return &ret; + } + } + ret = Item->execute(Cmd, ArgsAsIoTValue); // вызываем команду из модуля напрямую с передачей всех аргументов // if (ret.isDecimal) Serial.printf("Call from CallExprAST ID = %s, Command = %s, exec result = %f\n", Callee.c_str(), Cmd.c_str(), ret.valD); @@ -342,7 +357,10 @@ enum SysOp { sysop_getIP, sysop_mqttPub, sysop_getUptime, - sysop_mqttIsConnect + sysop_mqttIsConnect, + sysop_wifiIsConnect, + sysop_setInterval, + sysop_addPortMap }; IoTValue sysExecute(SysOp command, std::vector ¶m) { @@ -414,11 +432,11 @@ IoTValue sysExecute(SysOp command, std::vector ¶m) { case sysop_deepSleep: if (param.size()) { Serial.printf("Ушел спать на %d сек...", (int)param[0].valD); -#ifdef ESP32 +#if defined(ESP32) esp_sleep_enable_timer_wakeup(param[0].valD * 1000000); delay(1000); esp_deep_sleep_start(); -#else +#elif defined(ESP8266) ESP.deepSleep(param[0].valD * 1000000); #endif } @@ -435,7 +453,7 @@ IoTValue sysExecute(SysOp command, std::vector ¶m) { if (param.size() == 2) { // Serial.printf("Call from sysExecute %s %s\n", param[0].valS.c_str(), param[1].valS.c_str()); String tmpStr = param[1].valS; - if (param[1].isDecimal) tmpStr = param[1].valD; + if (param[1].isDecimal) tmpStr = String(param[1].valD); value.valD = mqtt.publish(param[0].valS.c_str(), tmpStr.c_str(), false); } break; @@ -446,6 +464,19 @@ IoTValue sysExecute(SysOp command, std::vector ¶m) { case sysop_mqttIsConnect: value.valD = mqttIsConnect(); break; + case sysop_wifiIsConnect: + value.valD = isNetworkActive(); + break; + case sysop_setInterval: + if (param.size() == 1) { + + } + break; + case sysop_addPortMap: + if (param.size() == 5) { + addPortMap(param[0].valS, param[1].valS, param[2].valD, param[3].valS, param[4].valD); + } + break; } return value; @@ -502,6 +533,12 @@ class SysCallExprAST : public ExprAST { operation = sysop_getUptime; else if (Callee == F("mqttIsConnect")) operation = sysop_mqttIsConnect; + else if (Callee == F("wifiIsConnect")) + operation = sysop_wifiIsConnect; + else if (Callee == F("setInterval")) + operation = sysop_setInterval; + else if (Callee == F("addPortMap")) + operation = sysop_addPortMap; else operation = sysop_notfound; } @@ -658,7 +695,7 @@ int IoTScenario::gettok() { LastChar = getLastChar(); if (isalpha(LastChar) || LastChar == '_') { // идентификатор: [a-zA-Z][a-zA-Z0-9]* - IdentifierStr = (char)LastChar; + IdentifierStr = String((char)LastChar); while (isalnum((LastChar = getLastChar())) || LastChar == '_') { IdentifierStr += (char)LastChar; } @@ -701,7 +738,18 @@ int IoTScenario::gettok() { IdentifierStr = ""; LastChar = getLastChar(); while (LastChar != '"' && LastChar != EOF) { - IdentifierStr += (char)LastChar; + if (LastChar == '\\') { // обработка экранированных символов в строке + LastChar = getLastChar(); + if (LastChar == '"') { + IdentifierStr += '"'; + } else if (LastChar == 'n') { + IdentifierStr += '\n'; + } else if (LastChar == '\\') { + IdentifierStr += '\\'; + } + } else { + IdentifierStr += (char)LastChar; + } LastChar = getLastChar(); } LastChar = getLastChar(); diff --git a/src/classes/IoTUart.cpp b/src/classes/IoTUart.cpp index d6678634..f4b39e65 100644 --- a/src/classes/IoTUart.cpp +++ b/src/classes/IoTUart.cpp @@ -9,11 +9,11 @@ IoTUart::IoTUart(const String& parameters) : IoTItem(parameters) { jsonRead(parameters, "speed", _speed); jsonRead(parameters, "line", _line); -#ifdef ESP8266 +#if defined (ESP8266) _myUART = new SoftwareSerial(_rx, _tx); _myUART->begin(_speed); #endif -#ifdef ESP32 +#if defined (ESP32) if (_line >= 0) { _myUART = new HardwareSerial(_line); ((HardwareSerial*)_myUART)->begin(_speed, SERIAL_8N1, _rx, _tx); @@ -22,6 +22,10 @@ IoTUart::IoTUart(const String& parameters) : IoTItem(parameters) { ((SoftwareSerial*)_myUART)->begin(_speed); } #endif +#if defined (LIBRETINY) + _myUART = new SerialClass(_rx, _tx); + _myUART->begin((unsigned long)_speed); +#endif } void IoTUart::loop() { diff --git a/src/modules/API.cpp b/src/modules/API.cpp index 5dd27955..c7609152 100644 --- a/src/modules/API.cpp +++ b/src/modules/API.cpp @@ -3,89 +3,63 @@ void* getAPI_Cron(String subtype, String params); void* getAPI_Loging(String subtype, String params); void* getAPI_LogingDaily(String subtype, String params); +void* getAPI_LogingHourly(String subtype, String params); void* getAPI_IoTMath(String subtype, String params); void* getAPI_owmWeather(String subtype, String params); void* getAPI_Ping(String subtype, String params); void* getAPI_Timer(String subtype, String params); +void* getAPI_UpdateServer(String subtype, String params); void* getAPI_Variable(String subtype, String params); void* getAPI_VButton(String subtype, String params); -void* getAPI_A02Distance(String subtype, String params); -void* getAPI_Acs712(String subtype, String params); void* getAPI_AhtXX(String subtype, String params); void* getAPI_AnalogAdc(String subtype, String params); -void* getAPI_BL0937(String subtype, String params); void* getAPI_Bme280(String subtype, String params); void* getAPI_Bmp280(String subtype, String params); void* getAPI_Dht1122(String subtype, String params); void* getAPI_Ds18b20(String subtype, String params); void* getAPI_Impulse(String subtype, String params); -void* getAPI_MQgas(String subtype, String params); -void* getAPI_Pzem004_v2(String subtype, String params); +void* getAPI_Ntc(String subtype, String params); void* getAPI_RTC(String subtype, String params); -void* getAPI_S8(String subtype, String params); -void* getAPI_Sht20(String subtype, String params); -void* getAPI_Sht30(String subtype, String params); -void* getAPI_Sonar(String subtype, String params); void* getAPI_UART(String subtype, String params); void* getAPI_AnalogBtn(String subtype, String params); void* getAPI_ButtonIn(String subtype, String params); void* getAPI_ButtonOut(String subtype, String params); -void* getAPI_Buzzer(String subtype, String params); void* getAPI_Encoder(String subtype, String params); -void* getAPI_IoTServo(String subtype, String params); -void* getAPI_Mcp23017(String subtype, String params); -void* getAPI_Mp3(String subtype, String params); void* getAPI_Multitouch(String subtype, String params); -void* getAPI_Pcf8574(String subtype, String params); -void* getAPI_Pwm8266(String subtype, String params); +void* getAPI_Pwm32(String subtype, String params); void* getAPI_TelegramLT(String subtype, String params); -void* getAPI_DwinI(String subtype, String params); -void* getAPI_Lcd2004(String subtype, String params); -void* getAPI_Oled64(String subtype, String params); +void* getAPI_Thermostat(String subtype, String params); void* getAPI(String subtype, String params) { -void* tmpAPI; -if ((tmpAPI = getAPI_Cron(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Loging(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_LogingDaily(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_IoTMath(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_owmWeather(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Ping(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Timer(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Variable(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_VButton(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_A02Distance(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Acs712(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_AhtXX(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_AnalogAdc(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_BL0937(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Bme280(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Bmp280(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Dht1122(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Ds18b20(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Impulse(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_MQgas(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Pzem004_v2(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_RTC(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_S8(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Sht20(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Sht30(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Sonar(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_UART(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_AnalogBtn(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_ButtonIn(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_ButtonOut(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Buzzer(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Encoder(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_IoTServo(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Mcp23017(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Mp3(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Multitouch(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Pcf8574(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Pwm8266(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_TelegramLT(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_DwinI(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Lcd2004(subtype, params)) != nullptr) return tmpAPI; -if ((tmpAPI = getAPI_Oled64(subtype, params)) != nullptr) return tmpAPI; -return nullptr; +void* tmpAPI; void* foundAPI = nullptr; +if ((tmpAPI = getAPI_Cron(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Loging(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_LogingDaily(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_LogingHourly(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_IoTMath(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_owmWeather(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Ping(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Timer(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_UpdateServer(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Variable(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_VButton(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_AhtXX(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_AnalogAdc(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Bme280(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Bmp280(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Dht1122(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Ds18b20(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Impulse(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Ntc(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_RTC(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_UART(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_AnalogBtn(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_ButtonIn(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_ButtonOut(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Encoder(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Multitouch(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Pwm32(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_TelegramLT(subtype, params)) != nullptr) foundAPI = tmpAPI; +if ((tmpAPI = getAPI_Thermostat(subtype, params)) != nullptr) foundAPI = tmpAPI; +return foundAPI; } \ No newline at end of file diff --git a/src/modules/display/DwinI/DwinI.cpp b/src/modules/display/DwinI/DwinI.cpp index 3df8fe8e..ccaa7c9f 100644 --- a/src/modules/display/DwinI/DwinI.cpp +++ b/src/modules/display/DwinI/DwinI.cpp @@ -140,6 +140,8 @@ class DwinI : public IoTUart { void onModuleOrder(String &key, String &value) { if (key == "uploadUI") { //SerialPrint("i", F("DwinI"), "Устанавливаем UI: " + value); + if (value != "") uartPrintHex(value.c_str()); + } } diff --git a/src/modules/display/DwinI/modinfo.json b/src/modules/display/DwinI/modinfo.json index c5a57f04..863ee10c 100644 --- a/src/modules/display/DwinI/modinfo.json +++ b/src/modules/display/DwinI/modinfo.json @@ -40,36 +40,12 @@ "btn-uploadUI": "Формирует автоматически графический интерфейс на базе конфигурации и выгружает в экран. Занимает продолжительное время! (в разработке)" } }, - "defActive": true, + "defActive": false, "usedLibs": { - "esp32_4mb": [ + "esp32*": [ "plerup/EspSoftwareSerial" ], - "esp32_4mb3f": [ - "plerup/EspSoftwareSerial" - ], - "esp32cam_4mb": [ - "plerup/EspSoftwareSerial" - ], - "esp8266_4mb": [ - "plerup/EspSoftwareSerial" - ], - "esp8266_1mb": [ - "plerup/EspSoftwareSerial" - ], - "esp8266_1mb_ota": [ - "plerup/EspSoftwareSerial" - ], - "esp8266_2mb": [ - "plerup/EspSoftwareSerial" - ], - "esp8266_2mb_ota": [ - "plerup/EspSoftwareSerial" - ], - "esp8285_1mb": [ - "plerup/EspSoftwareSerial" - ], - "esp8285_1mb_ota": [ + "esp82*": [ "plerup/EspSoftwareSerial" ] } diff --git a/src/modules/display/GyverLAMP/GyverLAMP.cpp b/src/modules/display/GyverLAMP/GyverLAMP.cpp new file mode 100644 index 00000000..9f1f8b7c --- /dev/null +++ b/src/modules/display/GyverLAMP/GyverLAMP.cpp @@ -0,0 +1,1309 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "FastLED.h" +#include "effects.h" +#include "matrix.h" +extern IoTGpio IoTgpio; + +class GyverLAMP : public IoTItem +{ +private: + unsigned long prevmillis = 0; + String curEffect; + +public: + //======================================================================================================= + // setup() + GyverLAMP(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("speed"), _speed); + jsonRead(parameters, F("dalay"), _dalay); + jsonRead(parameters, F("brightness"), _brightness); + // jsonRead(parameters, F("x_size"), _x_size); + // jsonRead(parameters, F("y_size"), _y_size); + + LEDS.setBrightness(_brightness); // ограничить максимальную яркость + LEDS.addLeds(leds, LED_COUNT); // настрйоки для нашей ленты (ленты на WS2811, WS2812, WS2812B) + one_color_all(0, 0, 0); // погасить все светодиоды + LEDS.show(); + } + + //======================================================================================================= + // doByInterval() + void doByInterval() + { + } + + //======================================================================================================= + // loop() + void loop() + { + + // yield(); + + if (millis() - prevmillis > _dalay) + { + + prevmillis = millis(); + thisdelay = _speed; + + if (curEffect == "rainbow_fade") + { + // rainbow_fade(); + ihue++; + if (ihue > 255) + { + ihue = 0; + } + for (int idex = 0; idex < LED_COUNT; idex++) + { + leds[idex] = CHSV(ihue, thissat, 255); + } + LEDS.show(); + } + if (curEffect == "rainbow_loop") + { + // rainbow_loop(); + idex++; + ihue = ihue + thisstep; + if (idex >= LED_COUNT) + { + idex = 0; + } + if (ihue > 255) + { + ihue = 0; + } + leds[idex] = CHSV(ihue, thissat, 255); + LEDS.show(); + } + if (curEffect == "random_burst") + { + // random_burst(); + idex = random(0, LED_COUNT); + ihue = random(0, 255); + leds[idex] = CHSV(ihue, thissat, 255); + LEDS.show(); + } + if (curEffect == "color_bounce") + { + // color_bounce(); + if (bouncedirection == 0) + { + idex = idex + 1; + if (idex == LED_COUNT) + { + bouncedirection = 1; + idex = idex - 1; + } + } + if (bouncedirection == 1) + { + idex = idex - 1; + if (idex == 0) + { + bouncedirection = 0; + } + } + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idex) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else + { + leds[i] = CHSV(0, 0, 0); + } + } + LEDS.show(); + } + if (curEffect == "color_bounceFADE") + { + // color_bounceFADE(); + if (bouncedirection == 0) + { + idex = idex + 1; + if (idex == LED_COUNT) + { + bouncedirection = 1; + idex = idex - 1; + } + } + if (bouncedirection == 1) + { + idex = idex - 1; + if (idex == 0) + { + bouncedirection = 0; + } + } + int iL1 = adjacent_cw(idex); + int iL2 = adjacent_cw(iL1); + int iL3 = adjacent_cw(iL2); + int iR1 = adjacent_ccw(idex); + int iR2 = adjacent_ccw(iR1); + int iR3 = adjacent_ccw(iR2); + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idex) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else if (i == iL1) + { + leds[i] = CHSV(thishue, thissat, 150); + } + else if (i == iL2) + { + leds[i] = CHSV(thishue, thissat, 80); + } + else if (i == iL3) + { + leds[i] = CHSV(thishue, thissat, 20); + } + else if (i == iR1) + { + leds[i] = CHSV(thishue, thissat, 150); + } + else if (i == iR2) + { + leds[i] = CHSV(thishue, thissat, 80); + } + else if (i == iR3) + { + leds[i] = CHSV(thishue, thissat, 20); + } + else + { + leds[i] = CHSV(0, 0, 0); + } + } + LEDS.show(); + } + if (curEffect == "ems_lightsONE") + { + // ems_lightsONE(); + idex++; + if (idex >= LED_COUNT) + { + idex = 0; + } + int idexR = idex; + int idexB = antipodal_index(idexR); + int thathue = (thishue + 160) % 255; + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idexR) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else if (i == idexB) + { + leds[i] = CHSV(thathue, thissat, 255); + } + else + { + leds[i] = CHSV(0, 0, 0); + } + } + LEDS.show(); + } + if (curEffect == "ems_lightsALL") + { + // ems_lightsALL(); + idex++; + if (idex >= LED_COUNT) + { + idex = 0; + } + int idexR = idex; + int idexB = antipodal_index(idexR); + int thathue = (thishue + 160) % 255; + leds[idexR] = CHSV(thishue, thissat, 255); + leds[idexB] = CHSV(thathue, thissat, 255); + LEDS.show(); + } + if (curEffect == "flicker") + { + flicker(); + } + + if (curEffect == "pulse_one_color_all") + { + // pulse_one_color_all(); + if (bouncedirection == 0) + { + ibright++; + if (ibright >= 255) + { + bouncedirection = 1; + } + } + if (bouncedirection == 1) + { + ibright = ibright - 1; + if (ibright <= 1) + { + bouncedirection = 0; + } + } + for (int idex = 0; idex < LED_COUNT; idex++) + { + leds[idex] = CHSV(thishue, thissat, ibright); + } + LEDS.show(); + } + if (curEffect == "pulse_one_color_all_rev") + { + // pulse_one_color_all_rev(); + if (bouncedirection == 0) + { + isat++; + if (isat >= 255) + { + bouncedirection = 1; + } + } + if (bouncedirection == 1) + { + isat = isat - 1; + if (isat <= 1) + { + bouncedirection = 0; + } + } + for (int idex = 0; idex < LED_COUNT; idex++) + { + leds[idex] = CHSV(thishue, isat, 255); + } + LEDS.show(); + } + if (curEffect == "fade_vertical") + { + // fade_vertical(); + idex++; + if (idex > TOP_INDEX) + { + idex = 0; + } + int idexA = idex; + int idexB = horizontal_index(idexA); + ibright = ibright + 10; + if (ibright > 255) + { + ibright = 0; + } + leds[idexA] = CHSV(thishue, thissat, ibright); + leds[idexB] = CHSV(thishue, thissat, ibright); + LEDS.show(); + } + if (curEffect == "rule30") + { + // rule30(); + if (bouncedirection == 0) + { + random_red(); + bouncedirection = 1; + } + copy_led_array(); + int iCW; + int iCCW; + int y = 100; + for (int i = 0; i < LED_COUNT; i++) + { + iCW = adjacent_cw(i); + iCCW = adjacent_ccw(i); + if (ledsX[iCCW][0] > y && ledsX[i][0] > y && ledsX[iCW][0] > y) + { + leds[i].r = 0; + } + if (ledsX[iCCW][0] > y && ledsX[i][0] > y && ledsX[iCW][0] <= y) + { + leds[i].r = 0; + } + if (ledsX[iCCW][0] > y && ledsX[i][0] <= y && ledsX[iCW][0] > y) + { + leds[i].r = 0; + } + if (ledsX[iCCW][0] > y && ledsX[i][0] <= y && ledsX[iCW][0] <= y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] > y && ledsX[iCW][0] > y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] > y && ledsX[iCW][0] <= y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] <= y && ledsX[iCW][0] > y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] <= y && ledsX[iCW][0] <= y) + { + leds[i].r = 0; + } + } + LEDS.show(); + } + if (curEffect == "random_march") + { + // random_march(); + copy_led_array(); + int iCCW; + leds[0] = CHSV(random(0, 255), 255, 255); + for (int idex = 1; idex < LED_COUNT; idex++) + { + iCCW = adjacent_ccw(idex); + leds[idex].r = ledsX[iCCW][0]; + leds[idex].g = ledsX[iCCW][1]; + leds[idex].b = ledsX[iCCW][2]; + } + LEDS.show(); + } + if (curEffect == "rwb_march") + { + // rwb_march(); + copy_led_array(); + int iCCW; + idex++; + if (idex > 2) + { + idex = 0; + } + switch (idex) + { + case 0: + leds[0].r = 255; + leds[0].g = 0; + leds[0].b = 0; + break; + case 1: + leds[0].r = 255; + leds[0].g = 255; + leds[0].b = 255; + break; + case 2: + leds[0].r = 0; + leds[0].g = 0; + leds[0].b = 255; + break; + } + for (int i = 1; i < LED_COUNT; i++) + { + iCCW = adjacent_ccw(i); + leds[i].r = ledsX[iCCW][0]; + leds[i].g = ledsX[iCCW][1]; + leds[i].b = ledsX[iCCW][2]; + } + LEDS.show(); + } + if (curEffect == "radiation") + { + // radiation(); + int N3 = int(LED_COUNT / 3); + int N6 = int(LED_COUNT / 6); + int N12 = int(LED_COUNT / 12); + for (int i = 0; i < N6; i++) + { //-HACKY, I KNOW... + tcount = tcount + .02; + if (tcount > 3.14) + { + tcount = 0.0; + } + ibright = int(sin(tcount) * 255); + int j0 = (i + LED_COUNT - N12) % LED_COUNT; + int j1 = (j0 + N3) % LED_COUNT; + int j2 = (j1 + N3) % LED_COUNT; + leds[j0] = CHSV(thishue, thissat, ibright); + leds[j1] = CHSV(thishue, thissat, ibright); + leds[j2] = CHSV(thishue, thissat, ibright); + } + LEDS.show(); + } + if (curEffect == "color_loop_vardelay") + { + // color_loop_vardelay(); + idex++; + if (idex > LED_COUNT) + { + idex = 0; + } + int di = abs(TOP_INDEX - idex); + int t = constrain((10 / di) * 10, 10, 500); + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idex) + { + leds[i] = CHSV(0, thissat, 255); + } + else + { + leds[i].r = 0; + leds[i].g = 0; + leds[i].b = 0; + } + } + LEDS.show(); + } + if (curEffect == "white_temps") + { + // white_temps(); + int N9 = int(LED_COUNT / 9); + for (int i = 0; i < LED_COUNT; i++) + { + if (i >= 0 && i < N9) + { + leds[i].r = 255; //-CANDLE - 1900 + leds[i].g = 147; + leds[i].b = 41; + } + if (i >= N9 && i < N9 * 2) + { + leds[i].r = 255; //-40W TUNG - 2600 + leds[i].g = 197; + leds[i].b = 143; + } + if (i >= N9 * 2 && i < N9 * 3) + { + leds[i].r = 255; //-100W TUNG - 2850 + leds[i].g = 214; + leds[i].b = 170; + } + if (i >= N9 * 3 && i < N9 * 4) + { + leds[i].r = 255; //-HALOGEN - 3200 + leds[i].g = 241; + leds[i].b = 224; + } + if (i >= N9 * 4 && i < N9 * 5) + { + leds[i].r = 255; //-CARBON ARC - 5200 + leds[i].g = 250; + leds[i].b = 244; + } + if (i >= N9 * 5 && i < N9 * 6) + { + leds[i].r = 255; //-HIGH NOON SUN - 5400 + leds[i].g = 255; + leds[i].b = 251; + } + if (i >= N9 * 6 && i < N9 * 7) + { + leds[i].r = 255; //-DIRECT SUN - 6000 + leds[i].g = 255; + leds[i].b = 255; + } + if (i >= N9 * 7 && i < N9 * 8) + { + leds[i].r = 201; //-OVERCAST SKY - 7000 + leds[i].g = 226; + leds[i].b = 255; + } + if (i >= N9 * 8 && i < LED_COUNT) + { + leds[i].r = 64; //-CLEAR BLUE SKY - 20000 + leds[i].g = 156; + leds[i].b = 255; + } + } + LEDS.show(); + } + if (curEffect == "sin_bright_wave") + { + sin_bright_wave(); + } + if (curEffect == "pop_horizontal") + { + // pop_horizontal(); + int ix; + if (bouncedirection == 0) + { + bouncedirection = 1; + ix = idex; + } + else if (bouncedirection == 1) + { + bouncedirection = 0; + ix = horizontal_index(idex); + idex++; + if (idex > TOP_INDEX) + { + idex = 0; + } + } + for (int i = 0; i < LED_COUNT; i++) + { + if (i == ix) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else + { + leds[i].r = 0; + leds[i].g = 0; + leds[i].b = 0; + } + } + LEDS.show(); + } + if (curEffect == "quad_bright_curve") + { + // quad_bright_curve(); + int ax; + for (int x = 0; x < LED_COUNT; x++) + { + if (x <= TOP_INDEX) + { + ax = x; + } + else if (x > TOP_INDEX) + { + ax = LED_COUNT - x; + } + int a = 1; + int b = 1; + int c = 0; + int iquad = -(ax * ax * a) + (ax * b) + c; //-ax2+bx+c + int hquad = -(TOP_INDEX * TOP_INDEX * a) + (TOP_INDEX * b) + c; + ibright = int((float(iquad) / float(hquad)) * 255); + leds[x] = CHSV(thishue, thissat, ibright); + } + LEDS.show(); + } + if (curEffect == "flame") + { + flame(); + } + if (curEffect == "rainbow_vertical") + { + // rainbow_vertical(); + idex++; + if (idex > TOP_INDEX) + { + idex = 0; + } + ihue = ihue + thisstep; + if (ihue > 255) + { + ihue = 0; + } + int idexA = idex; + int idexB = horizontal_index(idexA); + leds[idexA] = CHSV(ihue, thissat, 255); + leds[idexB] = CHSV(ihue, thissat, 255); + LEDS.show(); + } + if (curEffect == "pacman") + { + // pacman(); + int s = int(LED_COUNT / 4); + lcount++; + if (lcount > 5) + { + lcount = 0; + } + if (lcount == 0) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + } + if (lcount == 1 || lcount == 5) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + leds[s].r = 0; + leds[s].g = 0; + leds[s].b = 0; + } + if (lcount == 2 || lcount == 4) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + leds[s - 1].r = 0; + leds[s - 1].g = 0; + leds[s - 1].b = 0; + leds[s].r = 0; + leds[s].g = 0; + leds[s].b = 0; + leds[s + 1].r = 0; + leds[s + 1].g = 0; + leds[s + 1].b = 0; + } + if (lcount == 3) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + leds[s - 2].r = 0; + leds[s - 2].g = 0; + leds[s - 2].b = 0; + leds[s - 1].r = 0; + leds[s - 1].g = 0; + leds[s - 1].b = 0; + leds[s].r = 0; + leds[s].g = 0; + leds[s].b = 0; + leds[s + 1].r = 0; + leds[s + 1].g = 0; + leds[s + 1].b = 0; + leds[s + 2].r = 0; + leds[s + 2].g = 0; + leds[s + 2].b = 0; + } + LEDS.show(); + } + if (curEffect == "random_color_pop") + { + // random_color_pop(); + idex = random(0, LED_COUNT); + ihue = random(0, 255); + one_color_all(0, 0, 0); + leds[idex] = CHSV(ihue, thissat, 255); + LEDS.show(); + } + if (curEffect == "ems_lightsSTROBE") + { + ems_lightsSTROBE(); + } + if (curEffect == "Strobe") + { + Strobe(R, G, B, 10, 50, _speed); + } + + if (curEffect == "BouncingBalls") + { + BouncingBalls(R, G, B, 3); + } + if (curEffect == "BouncingColoredBalls") + { + byte colors[3][3] = {{0xff, 0, 0}, + {0xff, 0xff, 0xff}, + {0, 0, 0xff}}; + + BouncingColoredBalls(3, colors); + } + + if (curEffect == "rgb_propeller") + { + // rgb_propeller(); + idex++; + int ghue = (thishue + 80) % 255; + int bhue = (thishue + 160) % 255; + int N3 = int(LED_COUNT / 3); + int N6 = int(LED_COUNT / 6); + int N12 = int(LED_COUNT / 12); + for (int i = 0; i < N3; i++) + { + int j0 = (idex + i + LED_COUNT - N12) % LED_COUNT; + int j1 = (j0 + N3) % LED_COUNT; + int j2 = (j1 + N3) % LED_COUNT; + leds[j0] = CHSV(thishue, thissat, 255); + leds[j1] = CHSV(ghue, thissat, 255); + leds[j2] = CHSV(bhue, thissat, 255); + } + LEDS.show(); + } + if (curEffect == "kitt") + { + kitt(); + } + if (curEffect == "matrix") + { + // matrix(); + int rand = random(0, 100); + if (rand > 90) + { + leds[0] = CHSV(thishue, thissat, 255); + } + else + { + leds[0] = CHSV(thishue, thissat, 0); + } + copy_led_array(); + for (int i = 1; i < LED_COUNT; i++) + { + leds[i].r = ledsX[i - 1][0]; + leds[i].g = ledsX[i - 1][1]; + leds[i].b = ledsX[i - 1][2]; + } + LEDS.show(); + } + if (curEffect == "new_rainbow_loop") + { + // new_rainbow_loop(); + ihue -= 1; + fill_rainbow(leds, LED_COUNT, ihue); + LEDS.show(); + } + if (curEffect == "strip_march_ccw") + { + // strip_march_ccw(); + copy_led_array(); + int iCW; + for (int i = 0; i < LED_COUNT; i++) + { + iCW = adjacent_cw(i); + leds[i].r = ledsX[iCW][0]; + leds[i].g = ledsX[iCW][1]; + leds[i].b = ledsX[iCW][2]; + } + LEDS.show(); + } + if (curEffect == "strip_march_cw") + { + // strip_march_cw(); + copy_led_array(); + int iCCW; + for (int i = 0; i < LED_COUNT; i++) + { + iCCW = adjacent_ccw(i); + leds[i].r = ledsX[iCCW][0]; + leds[i].g = ledsX[iCCW][1]; + leds[i].b = ledsX[iCCW][2]; + } + LEDS.show(); + } + if (curEffect == "colorWipe") + { + colorWipe(R, G, B, thisdelay); + } + if (curEffect == "CylonBounce") + { + CylonBounce(R, G, B, 4, 10, thisdelay); + } + if (curEffect == "Fire") + { + // Fire(55, 120, thisdelay); + static byte heat[LED_COUNT]; + int cooldown; + int Cooling = 55, Sparking = 120; + // Step 1. Cool down every cell a little + for (int i = 0; i < LED_COUNT; i++) + { + cooldown = random(0, ((Cooling * 10) / LED_COUNT) + 2); + + if (cooldown > heat[i]) + { + heat[i] = 0; + } + else + { + heat[i] = heat[i] - cooldown; + } + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int k = LED_COUNT - 1; k >= 2; k--) + { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; + } + + // Step 3. Randomly ignite new 'sparks' near the bottom + if (random(255) < Sparking) + { + int y = random(7); + heat[y] = heat[y] + random(160, 255); + // heat[y] = random(160,255); + } + + // Step 4. Convert heat to LED colors + for (int j = 0; j < LED_COUNT; j++) + { + setPixelHeatColor(j, heat[j]); + } + + FastLED.show(); + } + if (curEffect == "NewKITT") + { + NewKITT(0xff, 0, 0, 8, 10, thisdelay); + } + if (curEffect == "rainbowCycle") + { + rainbowCycle(thisdelay); + } + if (curEffect == "TwinkleRandom") + { + TwinkleRandom(20, thisdelay, 1); + } + if (curEffect == "RunningLights") + { + RunningLights(0xff, 0xff, 0x00, thisdelay); + } + if (curEffect == "Sparkle") + { + Sparkle(random(255), random(255), random(60), _speed); + } + if (curEffect == "SnowSparkle") + { + SnowSparkle(0, 0, 0, 20, random(100, 1000)); + } + if (curEffect == "theaterChase") + { + theaterChase(0xff, 0, 0, _speed); + } + if (curEffect == "theaterChaseRainbow") + { + theaterChaseRainbow(_speed); + } + + if (curEffect == "demo_modeA") + { + demo_modeA(); + } + if (curEffect == "demo_modeB") + { + demo_modeB(); + } + if (curEffect == "meteorRain") + { + meteorRain(R, G, B, 10, 64, true, _speed); + } + + // матрицы + // *********** "дыхание" яркостью *********** + if (curEffect == "brightnessRoutine") + { + brightnessRoutine(); + } + // *********** снегопад 2.0 *********** + if (curEffect == "snowRoutine") + { + snowRoutine(); + } + // ***************************** БЛУДНЫЙ КУБИК ***************************** + if (curEffect == "ballRoutine") + { + ballRoutine(); + } + // *********** радуга заливка *********** + if (curEffect == "rainbowRoutine") + { + rainbowRoutine(); + } + // *********** радуга дигональная *********** + if (curEffect == "rainbowDiagonalRoutine") + { + rainbowDiagonalRoutine(); + } + // *********** радуга активных светодиодов (рисунка) *********** + if (curEffect == "rainbowColorsRoutine") + { + rainbowColorsRoutine(); + } + // ********************** огонь ********************** + if (curEffect == "fireRoutine") + { + fireRoutine(); + } + // **************** МАТРИЦА ***************** + if (curEffect == "matrixRoutine") + { + matrixRoutine(); + } + // ********************* ЗВЕЗДОПАД ****************** + if (curEffect == "starfallRoutine") + { + starfallRoutine(); + } + // рандомные гаснущие вспышки + if (curEffect == "sparklesRoutine") + { + sparklesRoutine(); + } + // ----------------------------- СВЕТЛЯКИ ------------------------------ + if (curEffect == "lightersRoutine") + { + lightersRoutine(); + } + // ------------- ПЕЙНТБОЛ ------------- + if (curEffect == "lightBallsRoutine") + { + lightBallsRoutine(); + } + // ------------- ВОДОВОРОТ ------------- + if (curEffect == "swirlRoutine") + { + swirlRoutine(); + } + // ------------- крутящаяся радуга матрица ------------- + if (curEffect == "rainbow_loop_matrix") + { + idex++; + ihue = ihue + thisstep; + if (idex >= LED_COUNT) + { + idex = 0; + } + if (ihue > 255) + { + ihue = 0; + } + + for (byte i = 0; i < WIDTH; i++) + { + CHSV thisColor = CHSV(ihue, thissat, 255); + for (byte j = 0; j < HEIGHT; j++) + drawPixelXY(i, j, thisColor); // leds[getPixelNumber(i, j)] = thisColor; + } + } + + FastLED.show(); + } + } + + IoTValue + execute(String command, std::vector ¶m) + { + + if (command == "effect") + { + curEffect = "none"; + + ///////////////////////////////////// + if (param[0].valS == "rainbow_fade") + { + curEffect = "rainbow_fade"; + } + if (param[0].valS == "rainbow_loop") + { + curEffect = "rainbow_loop"; + } + if (param[0].valS == "random_burst") + { + curEffect = "random_burst"; + } + if (param[0].valS == "color_bounce") + { + curEffect = "color_bounce"; + } + if (param[0].valS == "color_bounceFADE") + { + curEffect = "color_bounceFADE"; + } + if (param[0].valS == "ems_lightsONE") + { + curEffect = "ems_lightsONE"; + } + if (param[0].valS == "ems_lightsALL") + { + curEffect = "ems_lightsALL"; + } + if (param[0].valS == "pulse_one_color_all") + { + curEffect = "pulse_one_color_all"; + } + if (param[0].valS == "pulse_one_color_all_rev") + { + curEffect = "pulse_one_color_all_rev"; + } + if (param[0].valS == "fade_vertical") + { + curEffect = "fade_vertical"; + } + if (param[0].valS == "rule30") + { + curEffect = "rule30"; + } + if (param[0].valS == "random_march") + { + curEffect = "random_march"; + } + if (param[0].valS == "rwb_march") + { + curEffect = "rwb_march"; + } + if (param[0].valS == "radiation") + { + curEffect = "radiation"; + } + if (param[0].valS == "color_loop_vardelay") + { + curEffect = "color_loop_vardelay"; + } + if (param[0].valS == "white_temps") + { + curEffect = "white_temps"; + } + if (param[0].valS == "sin_bright_wave") + { + curEffect = "sin_bright_wave"; + } + if (param[0].valS == "pop_horizontal") + { + curEffect = "pop_horizontal"; + } + if (param[0].valS == "quad_bright_curve") + { + curEffect = "quad_bright_curve"; + } + if (param[0].valS == "flame") + { + curEffect = "flame"; + } + if (param[0].valS == "rainbow_vertical") + { + curEffect = "rainbow_vertical"; + } + if (param[0].valS == "pacman") + { + curEffect = "pacman"; + } + if (param[0].valS == "random_color_pop") + { + curEffect = "random_color_pop"; + } + if (param[0].valS == "ems_lightsSTROBE") + { + curEffect = "ems_lightsSTROBE"; + } + if (param[0].valS == "rgb_propeller") + { + curEffect = "rgb_propeller"; + } + if (param[0].valS == "kitt") + { + curEffect = "kitt"; + } + if (param[0].valS == "matrix") + { + curEffect = "matrix"; + } + if (param[0].valS == "new_rainbow_loop") + { + curEffect = "new_rainbow_loop"; + } + if (param[0].valS == "strip_march_ccw") + { + curEffect = "strip_march_ccw"; + } + if (param[0].valS == "strip_march_cw") + { + curEffect = "strip_march_cw"; + } + if (param[0].valS == "colorWipe") + { + curEffect = "colorWipe"; + } + if (param[0].valS == "CylonBounce") + { + curEffect = "CylonBounce"; + } + if (param[0].valS == "Fire") + { + curEffect = "Fire"; + } + if (param[0].valS == "NewKITT") + { + curEffect = "NewKITT"; + } + if (param[0].valS == "rainbowCycle") + { + curEffect = "rainbowCycle"; + } + if (param[0].valS == "TwinkleRandom") + { + curEffect = "TwinkleRandom"; + } + if (param[0].valS == "RunningLights") + { + curEffect = "RunningLights"; + } + if (param[0].valS == "Sparkle") + { + curEffect = "Sparkle"; + } + if (param[0].valS == "SnowSparkle") + { + curEffect = "SnowSparkle"; + } + if (param[0].valS == "theaterChase") + { + curEffect = "theaterChase"; + } + if (param[0].valS == "theaterChaseRainbow") + { + curEffect = "theaterChaseRainbow"; + } + if (param[0].valS == "Strobe") + { + curEffect = "Strobe"; + } + if (param[0].valS == "BouncingBalls") + { + curEffect = "BouncingBalls"; + } + if (param[0].valS == "BouncingColoredBalls") + { + curEffect = "BouncingColoredBalls"; + } + if (param[0].valS == "demo_modeA") + { + curEffect = "demo_modeA"; + } + if (param[0].valS == "demo_modeB") + { + curEffect = "demo_modeB"; + } + if (param[0].valS == "meteorRain") + { + curEffect = "meteorRain"; + } + + // матрицы + + // *********** "дыхание" яркостью *********** + if (param[0].valS == "brightnessRoutine") + { + curEffect = "brightnessRoutine"; + } + // *********** снегопад 2.0 *********** + if (param[0].valS == "snowRoutine") + { + curEffect = "snowRoutine"; + } + // ***************************** БЛУДНЫЙ КУБИК ***************************** + if (param[0].valS == "ballRoutine") + { + curEffect = "ballRoutine"; + } + // *********** радуга заливка *********** + if (param[0].valS == "rainbowRoutine") + { + curEffect = "rainbowRoutine"; + } + // *********** радуга дигональная *********** + if (param[0].valS == "rainbowDiagonalRoutine") + { + curEffect = "rainbowDiagonalRoutine"; + } + // *********** радуга активных светодиодов (рисунка) *********** + if (param[0].valS == "rainbowColorsRoutine") + { + curEffect = "rainbowColorsRoutine"; + } + // ********************** огонь ********************** + if (param[0].valS == "fireRoutine") + { + curEffect = "fireRoutine"; + } + // **************** МАТРИЦА ***************** + if (param[0].valS == "matrixRoutine") + { + curEffect = "matrixRoutine"; + } + // ********************* ЗВЕЗДОПАД ****************** + if (param[0].valS == "starfallRoutine") + { + curEffect = "starfallRoutine"; + } + // рандомные гаснущие вспышки + if (param[0].valS == "sparklesRoutine") + { + curEffect = "sparklesRoutine"; + } + // ----------------------------- СВЕТЛЯКИ ------------------------------ + if (param[0].valS == "lightersRoutine") + { + curEffect = "lightersRoutine"; + } + // ------------- ПЕЙНТБОЛ ------------- + if (param[0].valS == "lightBallsRoutine") + { + curEffect = "lightBallsRoutine"; + } + // ------------- ВОДОВОРОТ ------------- + if (param[0].valS == "swirlRoutine") + { + curEffect = "swirlRoutine"; + } + // ------------- крутящаяся радуга матрица ------------- + if (param[0].valS == "rainbow_loop_matrix") + { + curEffect = "rainbow_loop_matrix"; + } + + /////////////////////////////////////////////////// + } + else if (command == "brightness") + { + _brightness = param[0].valD; + LEDS.setBrightness(_brightness); + LEDS.show(); + } + else if (command == "speed") + { + _speed = param[0].valD; + } + else if (command == "dalay") + { + _dalay = param[0].valD; + } + else if (command == "color") + { + R = param[0].valD; + G = param[1].valD; + B = param[2].valD; + setAll(R, G, B); + LEDS.show(); + } + else if (command == "Off") + { + curEffect = "none"; + setAll(0, 0, 0); + LEDS.show(); + } + else if (command == "On") + { + curEffect = "none"; + + if (param.size() == 3) + { + R = param[0].valD; + G = param[1].valD; + B = param[2].valD; + setAll(R, G, B); + } + else + { + setAll(R, G, B); + } + LEDS.show(); + } + + return {}; + } + void setValue(const IoTValue &Value, bool genEvent = true) + { + value = Value; + _brightness = map(value.valD, 1, 1024, 1, 100); + LEDS.setBrightness(_brightness); + regEvent(value.valD, "GyverLAMP", false, genEvent); + // setAll(R, G, B); + LEDS.show(); + } + ~GyverLAMP(){}; +}; + +void *getAPI_GyverLAMP(String subtype, String param) +{ + if (subtype == F("GyverLAMP")) + { + return new GyverLAMP(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/display/GyverLAMP/config.h b/src/modules/display/GyverLAMP/config.h new file mode 100644 index 00000000..5fa713c4 --- /dev/null +++ b/src/modules/display/GyverLAMP/config.h @@ -0,0 +1,9 @@ + +// матрица +#define WIDTH 16 // ширина матрицы +#define HEIGHT 16 // высота матрицы +#define SEGMENTS 1 // диодов в одном "пикселе" (для создания матрицы из кусков ленты) +#define NUM_LEDS WIDTH *HEIGHT *SEGMENTS +// лента +#define LED_COUNT NUM_LEDS // число светодиодов в кольце/ленте +#define LED_DT 13 // пин, куда подключен DIN ленты \ No newline at end of file diff --git a/src/modules/display/GyverLAMP/effects.h b/src/modules/display/GyverLAMP/effects.h new file mode 100644 index 00000000..811f2b71 --- /dev/null +++ b/src/modules/display/GyverLAMP/effects.h @@ -0,0 +1,1833 @@ +#include "config.h" +CRGB leds[NUM_LEDS]; +// ---------------СЛУЖЕБНЫЕ ПЕРЕМЕННЫЕ----------------- +int _brightness; +int _speed, _dalay; +// int _x_size, _y_size; +int R = 255, G = 255, B = 255; +int BOTTOM_INDEX = 0; // светодиод начала отсчёта +int TOP_INDEX = int(LED_COUNT / 2); +int EVENODD = LED_COUNT % 2; +int ledsX[LED_COUNT][3]; //-ARRAY FOR COPYING WHATS IN THE LED STRIP CURRENTLY (FOR CELL-AUTOMATA, MARCH, ETC) + +int thisdelay = 20; //-FX LOOPS DELAY VAR +int thisstep = 10; //-FX LOOPS DELAY VAR +int thishue = 0; //-FX LOOPS DELAY VAR +int thissat = 255; //-FX LOOPS DELAY VAR + +int thisindex = 0; +int thisRED = 0; +int thisGRN = 0; +int thisBLU = 0; + +int idex = 0; //-LED INDEX (0 to LED_COUNT-1 +int ihue = 0; //-HUE (0-255) +int ibright = 0; //-BRIGHTNESS (0-255) +int isat = 0; //-SATURATION (0-255) +int bouncedirection = 0; //-SWITCH FOR COLOR BOUNCE (0-1) +float tcount = 0.0; //-INC VAR FOR SIN LOOPS +int lcount = 0; //-ANOTHER COUNTING VAR + +volatile boolean changeFlag; +// цвета мячиков для режима +byte ballColors[3][3] = { + {0xff, 0, 0}, + {0xff, 0xff, 0xff}, + {0, 0, 0xff}}; + +//------------------------------------- UTILITY FXNS -------------------------------------- +//---SET THE COLOR OF A SINGLE RGB LED +void set_color_led(int adex, int cred, int cgrn, int cblu) +{ + leds[adex].setRGB(cred, cgrn, cblu); +} + +//---FIND INDEX OF HORIZONAL OPPOSITE LED +int horizontal_index(int i) +{ + //-ONLY WORKS WITH INDEX < TOPINDEX + if (i == BOTTOM_INDEX) + { + return BOTTOM_INDEX; + } + if (i == TOP_INDEX && EVENODD == 1) + { + return TOP_INDEX + 1; + } + if (i == TOP_INDEX && EVENODD == 0) + { + return TOP_INDEX; + } + return LED_COUNT - i; +} + +//---FIND INDEX OF ANTIPODAL OPPOSITE LED +int antipodal_index(int i) +{ + int iN = i + TOP_INDEX; + if (i >= TOP_INDEX) + { + iN = (i + TOP_INDEX) % LED_COUNT; + } + return iN; +} + +//---FIND ADJACENT INDEX CLOCKWISE +int adjacent_cw(int i) +{ + int r; + if (i < LED_COUNT - 1) + { + r = i + 1; + } + else + { + r = 0; + } + return r; +} + +//---FIND ADJACENT INDEX COUNTER-CLOCKWISE +int adjacent_ccw(int i) +{ + int r; + if (i > 0) + { + r = i - 1; + } + else + { + r = LED_COUNT - 1; + } + return r; +} + +void copy_led_array() +{ + for (int i = 0; i < LED_COUNT; i++) + { + ledsX[i][0] = leds[i].r; + ledsX[i][1] = leds[i].g; + ledsX[i][2] = leds[i].b; + } +} + +void setPixel(int Pixel, byte red, byte green, byte blue) +{ + leds[Pixel].r = red; + leds[Pixel].g = green; + leds[Pixel].b = blue; +} +void one_color_all(int cred, int cgrn, int cblu) +{ //-SET ALL LEDS TO ONE COLOR + for (int i = 0; i < LED_COUNT; i++) + { + leds[i].setRGB(cred, cgrn, cblu); + } +} +void setAll(byte red, byte green, byte blue) +{ + + for (int i = 0; i < LED_COUNT; i++) + { + setPixel(i, red, green, blue); + } + + FastLED.show(); +} + +boolean safeDelay(int delTime) +{ + uint32_t thisTime = millis(); + while (millis() - thisTime <= delTime) + { + if (changeFlag) + { + changeFlag = false; + return true; + } + } + return false; +} +//------------------------LED EFFECT FUNCTIONS------------------------ + +void one_color_allHSV(int ahue) +{ //-SET ALL LEDS TO ONE COLOR (HSV) + for (int i = 0; i < LED_COUNT; i++) + { + leds[i] = CHSV(ahue, thissat, 255); + } +} + +void rainbow_fade() +{ //-m2-FADE ALL LEDS THROUGH HSV RAINBOW + ihue++; + if (ihue > 255) + { + ihue = 0; + } + for (int idex = 0; idex < LED_COUNT; idex++) + { + leds[idex] = CHSV(ihue, thissat, 255); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void rainbow_loop() +{ //-m3-LOOP HSV RAINBOW + idex++; + ihue = ihue + thisstep; + if (idex >= LED_COUNT) + { + idex = 0; + } + if (ihue > 255) + { + ihue = 0; + } + leds[idex] = CHSV(ihue, thissat, 255); + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void random_burst() +{ //-m4-RANDOM INDEX/COLOR + idex = random(0, LED_COUNT); + ihue = random(0, 255); + leds[idex] = CHSV(ihue, thissat, 255); + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void color_bounce() +{ //-m5-BOUNCE COLOR (SINGLE LED) + if (bouncedirection == 0) + { + idex = idex + 1; + if (idex == LED_COUNT) + { + bouncedirection = 1; + idex = idex - 1; + } + } + if (bouncedirection == 1) + { + idex = idex - 1; + if (idex == 0) + { + bouncedirection = 0; + } + } + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idex) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else + { + leds[i] = CHSV(0, 0, 0); + } + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void color_bounceFADE() +{ //-m6-BOUNCE COLOR (SIMPLE MULTI-LED FADE) + if (bouncedirection == 0) + { + idex = idex + 1; + if (idex == LED_COUNT) + { + bouncedirection = 1; + idex = idex - 1; + } + } + if (bouncedirection == 1) + { + idex = idex - 1; + if (idex == 0) + { + bouncedirection = 0; + } + } + int iL1 = adjacent_cw(idex); + int iL2 = adjacent_cw(iL1); + int iL3 = adjacent_cw(iL2); + int iR1 = adjacent_ccw(idex); + int iR2 = adjacent_ccw(iR1); + int iR3 = adjacent_ccw(iR2); + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idex) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else if (i == iL1) + { + leds[i] = CHSV(thishue, thissat, 150); + } + else if (i == iL2) + { + leds[i] = CHSV(thishue, thissat, 80); + } + else if (i == iL3) + { + leds[i] = CHSV(thishue, thissat, 20); + } + else if (i == iR1) + { + leds[i] = CHSV(thishue, thissat, 150); + } + else if (i == iR2) + { + leds[i] = CHSV(thishue, thissat, 80); + } + else if (i == iR3) + { + leds[i] = CHSV(thishue, thissat, 20); + } + else + { + leds[i] = CHSV(0, 0, 0); + } + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void ems_lightsONE() +{ //-m7-EMERGENCY LIGHTS (TWO COLOR SINGLE LED) + idex++; + if (idex >= LED_COUNT) + { + idex = 0; + } + int idexR = idex; + int idexB = antipodal_index(idexR); + int thathue = (thishue + 160) % 255; + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idexR) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else if (i == idexB) + { + leds[i] = CHSV(thathue, thissat, 255); + } + else + { + leds[i] = CHSV(0, 0, 0); + } + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void ems_lightsALL() +{ //-m8-EMERGENCY LIGHTS (TWO COLOR SOLID) + idex++; + if (idex >= LED_COUNT) + { + idex = 0; + } + int idexR = idex; + int idexB = antipodal_index(idexR); + int thathue = (thishue + 160) % 255; + leds[idexR] = CHSV(thishue, thissat, 255); + leds[idexB] = CHSV(thathue, thissat, 255); + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void flicker() +{ //-m9-FLICKER EFFECT + int random_bright = random(0, 255); + int random_delay = random(10, 100); + int random_bool = random(0, random_bright); + if (random_bool < 10) + { + for (int i = 0; i < LED_COUNT; i++) + { + leds[i] = CHSV(thishue, thissat, random_bright); + } + LEDS.show(); + if (safeDelay(random_delay)) + return; + } +} + +void pulse_one_color_all() +{ //-m10-PULSE BRIGHTNESS ON ALL LEDS TO ONE COLOR + if (bouncedirection == 0) + { + ibright++; + if (ibright >= 255) + { + bouncedirection = 1; + } + } + if (bouncedirection == 1) + { + ibright = ibright - 1; + if (ibright <= 1) + { + bouncedirection = 0; + } + } + for (int idex = 0; idex < LED_COUNT; idex++) + { + leds[idex] = CHSV(thishue, thissat, ibright); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void pulse_one_color_all_rev() +{ //-m11-PULSE SATURATION ON ALL LEDS TO ONE COLOR + if (bouncedirection == 0) + { + isat++; + if (isat >= 255) + { + bouncedirection = 1; + } + } + if (bouncedirection == 1) + { + isat = isat - 1; + if (isat <= 1) + { + bouncedirection = 0; + } + } + for (int idex = 0; idex < LED_COUNT; idex++) + { + leds[idex] = CHSV(thishue, isat, 255); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void fade_vertical() +{ //-m12-FADE 'UP' THE LOOP + idex++; + if (idex > TOP_INDEX) + { + idex = 0; + } + int idexA = idex; + int idexB = horizontal_index(idexA); + ibright = ibright + 10; + if (ibright > 255) + { + ibright = 0; + } + leds[idexA] = CHSV(thishue, thissat, ibright); + leds[idexB] = CHSV(thishue, thissat, ibright); + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} +//? +void random_red() +{ // QUICK 'N DIRTY RANDOMIZE TO GET CELL AUTOMATA STARTED + int temprand; + for (int i = 0; i < LED_COUNT; i++) + { + temprand = random(0, 100); + if (temprand > 50) + { + leds[i].r = 255; + } + if (temprand <= 50) + { + leds[i].r = 0; + } + leds[i].b = 0; + leds[i].g = 0; + } + LEDS.show(); +} + +void rule30() +{ //-m13-1D CELLULAR AUTOMATA - RULE 30 (RED FOR NOW) + if (bouncedirection == 0) + { + random_red(); + bouncedirection = 1; + } + copy_led_array(); + int iCW; + int iCCW; + int y = 100; + for (int i = 0; i < LED_COUNT; i++) + { + iCW = adjacent_cw(i); + iCCW = adjacent_ccw(i); + if (ledsX[iCCW][0] > y && ledsX[i][0] > y && ledsX[iCW][0] > y) + { + leds[i].r = 0; + } + if (ledsX[iCCW][0] > y && ledsX[i][0] > y && ledsX[iCW][0] <= y) + { + leds[i].r = 0; + } + if (ledsX[iCCW][0] > y && ledsX[i][0] <= y && ledsX[iCW][0] > y) + { + leds[i].r = 0; + } + if (ledsX[iCCW][0] > y && ledsX[i][0] <= y && ledsX[iCW][0] <= y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] > y && ledsX[iCW][0] > y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] > y && ledsX[iCW][0] <= y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] <= y && ledsX[iCW][0] > y) + { + leds[i].r = 255; + } + if (ledsX[iCCW][0] <= y && ledsX[i][0] <= y && ledsX[iCW][0] <= y) + { + leds[i].r = 0; + } + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void random_march() +{ //-m14-RANDOM MARCH CCW + copy_led_array(); + int iCCW; + leds[0] = CHSV(random(0, 255), 255, 255); + for (int idex = 1; idex < LED_COUNT; idex++) + { + iCCW = adjacent_ccw(idex); + leds[idex].r = ledsX[iCCW][0]; + leds[idex].g = ledsX[iCCW][1]; + leds[idex].b = ledsX[iCCW][2]; + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void rwb_march() +{ //-m15-R,W,B MARCH CCW + copy_led_array(); + int iCCW; + idex++; + if (idex > 2) + { + idex = 0; + } + switch (idex) + { + case 0: + leds[0].r = 255; + leds[0].g = 0; + leds[0].b = 0; + break; + case 1: + leds[0].r = 255; + leds[0].g = 255; + leds[0].b = 255; + break; + case 2: + leds[0].r = 0; + leds[0].g = 0; + leds[0].b = 255; + break; + } + for (int i = 1; i < LED_COUNT; i++) + { + iCCW = adjacent_ccw(i); + leds[i].r = ledsX[iCCW][0]; + leds[i].g = ledsX[iCCW][1]; + leds[i].b = ledsX[iCCW][2]; + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void radiation() +{ //-m16-SORT OF RADIATION SYMBOLISH- + int N3 = int(LED_COUNT / 3); + int N6 = int(LED_COUNT / 6); + int N12 = int(LED_COUNT / 12); + for (int i = 0; i < N6; i++) + { //-HACKY, I KNOW... + tcount = tcount + .02; + if (tcount > 3.14) + { + tcount = 0.0; + } + ibright = int(sin(tcount) * 255); + int j0 = (i + LED_COUNT - N12) % LED_COUNT; + int j1 = (j0 + N3) % LED_COUNT; + int j2 = (j1 + N3) % LED_COUNT; + leds[j0] = CHSV(thishue, thissat, ibright); + leds[j1] = CHSV(thishue, thissat, ibright); + leds[j2] = CHSV(thishue, thissat, ibright); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void color_loop_vardelay() +{ //-m17-COLOR LOOP (SINGLE LED) w/ VARIABLE DELAY + idex++; + if (idex > LED_COUNT) + { + idex = 0; + } + int di = abs(TOP_INDEX - idex); + int t = constrain((10 / di) * 10, 10, 500); + for (int i = 0; i < LED_COUNT; i++) + { + if (i == idex) + { + leds[i] = CHSV(0, thissat, 255); + } + else + { + leds[i].r = 0; + leds[i].g = 0; + leds[i].b = 0; + } + } + LEDS.show(); + if (safeDelay(t)) + return; +} + +void white_temps() +{ //-m18-SHOW A SAMPLE OF BLACK BODY RADIATION COLOR TEMPERATURES + int N9 = int(LED_COUNT / 9); + for (int i = 0; i < LED_COUNT; i++) + { + if (i >= 0 && i < N9) + { + leds[i].r = 255; //-CANDLE - 1900 + leds[i].g = 147; + leds[i].b = 41; + } + if (i >= N9 && i < N9 * 2) + { + leds[i].r = 255; //-40W TUNG - 2600 + leds[i].g = 197; + leds[i].b = 143; + } + if (i >= N9 * 2 && i < N9 * 3) + { + leds[i].r = 255; //-100W TUNG - 2850 + leds[i].g = 214; + leds[i].b = 170; + } + if (i >= N9 * 3 && i < N9 * 4) + { + leds[i].r = 255; //-HALOGEN - 3200 + leds[i].g = 241; + leds[i].b = 224; + } + if (i >= N9 * 4 && i < N9 * 5) + { + leds[i].r = 255; //-CARBON ARC - 5200 + leds[i].g = 250; + leds[i].b = 244; + } + if (i >= N9 * 5 && i < N9 * 6) + { + leds[i].r = 255; //-HIGH NOON SUN - 5400 + leds[i].g = 255; + leds[i].b = 251; + } + if (i >= N9 * 6 && i < N9 * 7) + { + leds[i].r = 255; //-DIRECT SUN - 6000 + leds[i].g = 255; + leds[i].b = 255; + } + if (i >= N9 * 7 && i < N9 * 8) + { + leds[i].r = 201; //-OVERCAST SKY - 7000 + leds[i].g = 226; + leds[i].b = 255; + } + if (i >= N9 * 8 && i < LED_COUNT) + { + leds[i].r = 64; //-CLEAR BLUE SKY - 20000 + leds[i].g = 156; + leds[i].b = 255; + } + } + LEDS.show(); + delay(100); +} +//?? +void sin_bright_wave() +{ //-m19-BRIGHTNESS SINE WAVE + for (int i = 0; i < LED_COUNT; i++) + { + tcount = tcount + .1; + if (tcount > 3.14) + { + tcount = 0.0; + } + ibright = int(sin(tcount) * 255); + leds[i] = CHSV(thishue, thissat, ibright); + LEDS.show(); + if (safeDelay(thisdelay)) + return; + } +} + +void pop_horizontal() +{ //-m20-POP FROM LEFT TO RIGHT UP THE RING + int ix; + if (bouncedirection == 0) + { + bouncedirection = 1; + ix = idex; + } + else if (bouncedirection == 1) + { + bouncedirection = 0; + ix = horizontal_index(idex); + idex++; + if (idex > TOP_INDEX) + { + idex = 0; + } + } + for (int i = 0; i < LED_COUNT; i++) + { + if (i == ix) + { + leds[i] = CHSV(thishue, thissat, 255); + } + else + { + leds[i].r = 0; + leds[i].g = 0; + leds[i].b = 0; + } + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void quad_bright_curve() +{ //-m21-QUADRATIC BRIGHTNESS CURVER + int ax; + for (int x = 0; x < LED_COUNT; x++) + { + if (x <= TOP_INDEX) + { + ax = x; + } + else if (x > TOP_INDEX) + { + ax = LED_COUNT - x; + } + int a = 1; + int b = 1; + int c = 0; + int iquad = -(ax * ax * a) + (ax * b) + c; //-ax2+bx+c + int hquad = -(TOP_INDEX * TOP_INDEX * a) + (TOP_INDEX * b) + c; + ibright = int((float(iquad) / float(hquad)) * 255); + leds[x] = CHSV(thishue, thissat, ibright); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} +//?? +void flame() +{ //-m22-FLAMEISH EFFECT + int idelay = random(0, 35); + float hmin = 0.1; + float hmax = 45.0; + float hdif = hmax - hmin; + int randtemp = random(0, 3); + float hinc = (hdif / float(TOP_INDEX)) + randtemp; + int ihue = hmin; + for (int i = 0; i <= TOP_INDEX; i++) + { + ihue = ihue + hinc; + leds[i] = CHSV(ihue, thissat, 255); + int ih = horizontal_index(i); + leds[ih] = CHSV(ihue, thissat, 255); + leds[TOP_INDEX].r = 255; + leds[TOP_INDEX].g = 255; + leds[TOP_INDEX].b = 255; + LEDS.show(); + if (safeDelay(idelay)) + return; + } +} + +void rainbow_vertical() +{ //-m23-RAINBOW 'UP' THE LOOP + idex++; + if (idex > TOP_INDEX) + { + idex = 0; + } + ihue = ihue + thisstep; + if (ihue > 255) + { + ihue = 0; + } + int idexA = idex; + int idexB = horizontal_index(idexA); + leds[idexA] = CHSV(ihue, thissat, 255); + leds[idexB] = CHSV(ihue, thissat, 255); + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void pacman() +{ //-m24-REALLY TERRIBLE PACMAN CHOMPING EFFECT + int s = int(LED_COUNT / 4); + lcount++; + if (lcount > 5) + { + lcount = 0; + } + if (lcount == 0) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + } + if (lcount == 1 || lcount == 5) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + leds[s].r = 0; + leds[s].g = 0; + leds[s].b = 0; + } + if (lcount == 2 || lcount == 4) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + leds[s - 1].r = 0; + leds[s - 1].g = 0; + leds[s - 1].b = 0; + leds[s].r = 0; + leds[s].g = 0; + leds[s].b = 0; + leds[s + 1].r = 0; + leds[s + 1].g = 0; + leds[s + 1].b = 0; + } + if (lcount == 3) + { + for (int i = 0; i < LED_COUNT; i++) + { + set_color_led(i, 255, 255, 0); + } + leds[s - 2].r = 0; + leds[s - 2].g = 0; + leds[s - 2].b = 0; + leds[s - 1].r = 0; + leds[s - 1].g = 0; + leds[s - 1].b = 0; + leds[s].r = 0; + leds[s].g = 0; + leds[s].b = 0; + leds[s + 1].r = 0; + leds[s + 1].g = 0; + leds[s + 1].b = 0; + leds[s + 2].r = 0; + leds[s + 2].g = 0; + leds[s + 2].b = 0; + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void random_color_pop() +{ //-m25-RANDOM COLOR POP + idex = random(0, LED_COUNT); + ihue = random(0, 255); + one_color_all(0, 0, 0); + leds[idex] = CHSV(ihue, thissat, 255); + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} +//?? +void ems_lightsSTROBE() +{ //-m26-EMERGENCY LIGHTS (STROBE LEFT/RIGHT) + int thishue = 0; + int thathue = (thishue + 160) % 255; + for (int x = 0; x < 5; x++) + { + for (int i = 0; i < TOP_INDEX; i++) + { + leds[i] = CHSV(thishue, thissat, 255); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; + one_color_all(0, 0, 0); + LEDS.show(); + if (safeDelay(thisdelay)) + return; + } + for (int x = 0; x < 5; x++) + { + for (int i = TOP_INDEX; i < LED_COUNT; i++) + { + leds[i] = CHSV(thathue, thissat, 255); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; + one_color_all(0, 0, 0); + LEDS.show(); + if (safeDelay(thisdelay)) + return; + } +} + +void rgb_propeller() +{ //-m27-RGB PROPELLER + idex++; + int ghue = (thishue + 80) % 255; + int bhue = (thishue + 160) % 255; + int N3 = int(LED_COUNT / 3); + int N6 = int(LED_COUNT / 6); + int N12 = int(LED_COUNT / 12); + for (int i = 0; i < N3; i++) + { + int j0 = (idex + i + LED_COUNT - N12) % LED_COUNT; + int j1 = (j0 + N3) % LED_COUNT; + int j2 = (j1 + N3) % LED_COUNT; + leds[j0] = CHSV(thishue, thissat, 255); + leds[j1] = CHSV(ghue, thissat, 255); + leds[j2] = CHSV(bhue, thissat, 255); + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} +//?? +void kitt() +{ //-m28-KNIGHT INDUSTIES 2000 + int rand = random(0, TOP_INDEX); + for (int i = 0; i < rand; i++) + { + leds[TOP_INDEX + i] = CHSV(thishue, thissat, 255); + leds[TOP_INDEX - i] = CHSV(thishue, thissat, 255); + LEDS.show(); + if (safeDelay(thisdelay / rand)) + return; + } + for (int i = rand; i > 0; i--) + { + leds[TOP_INDEX + i] = CHSV(thishue, thissat, 0); + leds[TOP_INDEX - i] = CHSV(thishue, thissat, 0); + LEDS.show(); + if (safeDelay(thisdelay / rand)) + return; + } +} + +void matrix() +{ //-m29-ONE LINE MATRIX + int rand = random(0, 100); + if (rand > 90) + { + leds[0] = CHSV(thishue, thissat, 255); + } + else + { + leds[0] = CHSV(thishue, thissat, 0); + } + copy_led_array(); + for (int i = 1; i < LED_COUNT; i++) + { + leds[i].r = ledsX[i - 1][0]; + leds[i].g = ledsX[i - 1][1]; + leds[i].b = ledsX[i - 1][2]; + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void strip_march_cw() +{ //-m50-MARCH STRIP CW + copy_led_array(); + int iCW; + for (int i = 0; i < LED_COUNT; i++) + { + iCW = adjacent_cw(i); + leds[i].r = ledsX[iCW][0]; + leds[i].g = ledsX[iCW][1]; + leds[i].b = ledsX[iCW][2]; + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void strip_march_ccw() +{ //-m51-MARCH STRIP CCW + copy_led_array(); + int iCCW; + for (int i = 0; i < LED_COUNT; i++) + { + iCCW = adjacent_ccw(i); + leds[i].r = ledsX[iCCW][0]; + leds[i].g = ledsX[iCCW][1]; + leds[i].b = ledsX[iCCW][2]; + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void new_rainbow_loop() +{ //-m88-RAINBOW FADE FROM FAST_SPI2 + ihue -= 1; + fill_rainbow(leds, LED_COUNT, ihue); + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} + +void demo_modeB() +{ + int r = 10; + one_color_all(0, 0, 0); + LEDS.show(); + thisdelay = 35; + for (int i = 0; i < r * 10; i++) + { + random_color_pop(); + } + for (int i = 0; i < r / 2; i++) + { + ems_lightsSTROBE(); + } + thisdelay = 50; + for (int i = 0; i < r * 10; i++) + { + rgb_propeller(); + } + one_color_all(0, 0, 0); + LEDS.show(); + thisdelay = 100; + thishue = 0; + for (int i = 0; i < r * 3; i++) + { + kitt(); + } + one_color_all(0, 0, 0); + LEDS.show(); + thisdelay = 30; + thishue = 95; + for (int i = 0; i < r * 25; i++) + { + matrix(); + } + one_color_all(0, 0, 0); + LEDS.show(); +} +void demo_modeA() +{ + int r = 10; + thisdelay = 20; + thisstep = 10; + thishue = 0; + thissat = 255; + one_color_all(255, 255, 255); + LEDS.show(); + if (safeDelay(1200)) + return; + for (int i = 0; i < r * 25; i++) + { + rainbow_fade(); + } + for (int i = 0; i < r * 20; i++) + { + rainbow_loop(); + } + for (int i = 0; i < r * 20; i++) + { + random_burst(); + } + for (int i = 0; i < r * 12; i++) + { + color_bounce(); + } + thisdelay = 40; + for (int i = 0; i < r * 12; i++) + { + color_bounceFADE(); + } + for (int i = 0; i < r * 6; i++) + { + ems_lightsONE(); + } + for (int i = 0; i < r * 5; i++) + { + ems_lightsALL(); + } + thishue = 160; + thissat = 50; + for (int i = 0; i < r * 40; i++) + { + flicker(); + } + one_color_all(0, 0, 0); + LEDS.show(); + thisdelay = 15; + thishue = 0; + thissat = 255; + for (int i = 0; i < r * 50; i++) + { + pulse_one_color_all(); + } + for (int i = 0; i < r * 40; i++) + { + pulse_one_color_all_rev(); + } + thisdelay = 60; + thishue = 180; + for (int i = 0; i < r * 5; i++) + { + fade_vertical(); + } + random_red(); + thisdelay = 100; + for (int i = 0; i < r * 5; i++) + { + rule30(); + } + thisdelay = 40; + for (int i = 0; i < r * 8; i++) + { + random_march(); + } + thisdelay = 80; + for (int i = 0; i < r * 5; i++) + { + rwb_march(); + } + one_color_all(0, 0, 0); + ; + LEDS.show(); + thisdelay = 60; + thishue = 95; + for (int i = 0; i < r * 15; i++) + { + radiation(); + } + for (int i = 0; i < r * 15; i++) + { + color_loop_vardelay(); + } + for (int i = 0; i < r * 5; i++) + { + white_temps(); + } + thisdelay = 35; + thishue = 180; + for (int i = 0; i < r; i++) + { + sin_bright_wave(); + } + thisdelay = 100; + thishue = 0; + for (int i = 0; i < r * 5; i++) + { + pop_horizontal(); + } + thisdelay = 100; + thishue = 180; + for (int i = 0; i < r * 4; i++) + { + quad_bright_curve(); + } + one_color_all(0, 0, 0); + LEDS.show(); + for (int i = 0; i < r * 3; i++) + { + flame(); + } + thisdelay = 50; + for (int i = 0; i < r * 10; i++) + { + pacman(); + } + thisdelay = 50; + thisstep = 15; + for (int i = 0; i < r * 12; i++) + { + rainbow_vertical(); + } + thisdelay = 100; + for (int i = 0; i < r * 3; i++) + { + strip_march_ccw(); + } + for (int i = 0; i < r * 3; i++) + { + strip_march_cw(); + } + demo_modeB(); + thisdelay = 5; + for (int i = 0; i < r * 120; i++) + { + new_rainbow_loop(); + } + one_color_all(255, 0, 0); + LEDS.show(); + if (safeDelay(1200)) + return; + one_color_all(0, 255, 0); + LEDS.show(); + if (safeDelay(1200)) + return; + one_color_all(0, 0, 255); + LEDS.show(); + if (safeDelay(1200)) + return; + one_color_all(255, 255, 0); + LEDS.show(); + if (safeDelay(1200)) + return; + one_color_all(0, 255, 255); + LEDS.show(); + if (safeDelay(1200)) + return; + one_color_all(255, 0, 255); + LEDS.show(); + if (safeDelay(1200)) + return; +} +//?? +//-----------------------------плавное заполнение цветом----------------------------------------- +void colorWipe(byte red, byte green, byte blue, int SpeedDelay) +{ + for (uint16_t i = 0; i < LED_COUNT; i++) + { + setPixel(i, red, green, blue); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } +} +//?? +//-----------------------------------бегающие светодиоды----------------------------------- +void CylonBounce(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) +{ + + for (int i = 0; i < LED_COUNT - EyeSize - 2; i++) + { + setAll(0, 0, 0); + setPixel(i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(i + j, red, green, blue); + } + setPixel(i + EyeSize + 1, red / 10, green / 10, blue / 10); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } + + if (safeDelay(ReturnDelay)) + return; + + for (int i = LED_COUNT - EyeSize - 2; i > 0; i--) + { + setAll(0, 0, 0); + setPixel(i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(i + j, red, green, blue); + } + setPixel(i + EyeSize + 1, red / 10, green / 10, blue / 10); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } + + if (safeDelay(ReturnDelay)) + return; +} + +//---------------------------------линейный огонь------------------------------------- + +void setPixelHeatColor(int Pixel, byte temperature) +{ + // Scale 'heat' down from 0-255 to 0-191 + byte t192 = round((temperature / 255.0) * 191); + + // calculate ramp up from + byte heatramp = t192 & 0x3F; // 0..63 + heatramp <<= 2; // scale up to 0..252 + + // figure out which third of the spectrum we're in: + if (t192 > 0x80) + { // hottest + setPixel(Pixel, 255, 255, heatramp); + } + else if (t192 > 0x40) + { // middle + setPixel(Pixel, 255, heatramp, 0); + } + else + { // coolest + setPixel(Pixel, heatramp, 0, 0); + } +} +void Fire(int Cooling, int Sparking, int SpeedDelay) +{ + static byte heat[LED_COUNT]; + int cooldown; + + // Step 1. Cool down every cell a little + for (int i = 0; i < LED_COUNT; i++) + { + cooldown = random(0, ((Cooling * 10) / LED_COUNT) + 2); + + if (cooldown > heat[i]) + { + heat[i] = 0; + } + else + { + heat[i] = heat[i] - cooldown; + } + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int k = LED_COUNT - 1; k >= 2; k--) + { + heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; + } + + // Step 3. Randomly ignite new 'sparks' near the bottom + if (random(255) < Sparking) + { + int y = random(7); + heat[y] = heat[y] + random(160, 255); + // heat[y] = random(160,255); + } + + // Step 4. Convert heat to LED colors + for (int j = 0; j < LED_COUNT; j++) + { + setPixelHeatColor(j, heat[j]); + } + + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; +} + +//-------------------------------newKITT--------------------------------------- + +void CenterToOutside(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) +{ + for (int i = ((LED_COUNT - EyeSize) / 2); i >= 0; i--) + { + setAll(0, 0, 0); + + setPixel(i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(i + j, red, green, blue); + } + setPixel(i + EyeSize + 1, red / 10, green / 10, blue / 10); + + setPixel(LED_COUNT - i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(LED_COUNT - i - j, red, green, blue); + } + setPixel(LED_COUNT - i - EyeSize - 1, red / 10, green / 10, blue / 10); + + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } + if (safeDelay(ReturnDelay)) + return; +} + +void OutsideToCenter(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) +{ + for (int i = 0; i <= ((LED_COUNT - EyeSize) / 2); i++) + { + setAll(0, 0, 0); + + setPixel(i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(i + j, red, green, blue); + } + setPixel(i + EyeSize + 1, red / 10, green / 10, blue / 10); + + setPixel(LED_COUNT - i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(LED_COUNT - i - j, red, green, blue); + } + setPixel(LED_COUNT - i - EyeSize - 1, red / 10, green / 10, blue / 10); + + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } + if (safeDelay(ReturnDelay)) + return; +} + +void LeftToRight(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) +{ + for (int i = 0; i < LED_COUNT - EyeSize - 2; i++) + { + setAll(0, 0, 0); + setPixel(i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(i + j, red, green, blue); + } + setPixel(i + EyeSize + 1, red / 10, green / 10, blue / 10); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } + if (safeDelay(ReturnDelay)) + return; +} + +void RightToLeft(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) +{ + for (int i = LED_COUNT - EyeSize - 2; i > 0; i--) + { + setAll(0, 0, 0); + setPixel(i, red / 10, green / 10, blue / 10); + for (int j = 1; j <= EyeSize; j++) + { + setPixel(i + j, red, green, blue); + } + setPixel(i + EyeSize + 1, red / 10, green / 10, blue / 10); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } + if (safeDelay(ReturnDelay)) + return; +} +void NewKITT(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay) +{ + RightToLeft(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); + LeftToRight(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); + OutsideToCenter(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); + CenterToOutside(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); + LeftToRight(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); + RightToLeft(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); + OutsideToCenter(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); + CenterToOutside(red, green, blue, EyeSize, SpeedDelay, ReturnDelay); +} + +//-------------------------------rainbowCycle--------------------------------------- +//?? +byte *Wheel(byte WheelPos) +{ + static byte c[3]; + + if (WheelPos < 85) + { + c[0] = WheelPos * 3; + c[1] = 255 - WheelPos * 3; + c[2] = 0; + } + else if (WheelPos < 170) + { + WheelPos -= 85; + c[0] = 255 - WheelPos * 3; + c[1] = 0; + c[2] = WheelPos * 3; + } + else + { + WheelPos -= 170; + c[0] = 0; + c[1] = WheelPos * 3; + c[2] = 255 - WheelPos * 3; + } + + return c; +} + +void rainbowCycle(int SpeedDelay) +{ + byte *c; + uint16_t i, j; + + for (j = 0; j < 256 * 5; j++) + { // 5 cycles of all colors on wheel + for (i = 0; i < LED_COUNT; i++) + { + c = Wheel(((i * 256 / LED_COUNT) + j) & 255); + setPixel(i, *c, *(c + 1), *(c + 2)); + } + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } +} +//?? +//-------------------------------TwinkleRandom--------------------------------------- +void TwinkleRandom(int Count, int SpeedDelay, boolean OnlyOne) +{ + setAll(0, 0, 0); + + for (int i = 0; i < Count; i++) + { + setPixel(random(LED_COUNT), random(0, 255), random(0, 255), random(0, 255)); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + if (OnlyOne) + { + setAll(0, 0, 0); + } + } + + if (safeDelay(SpeedDelay)) + return; +} + +//-------------------------------RunningLights--------------------------------------- +void RunningLights(byte red, byte green, byte blue, int WaveDelay) +{ + int Position = 0; + + for (int i = 0; i < LED_COUNT * 2; i++) + { + Position++; // = 0; //Position + Rate; + for (int i = 0; i < LED_COUNT; i++) + { + // sine wave, 3 offset waves make a rainbow! + // float level = sin(i+Position) * 127 + 128; + // setPixel(i,level,0,0); + // float level = sin(i+Position) * 127 + 128; + setPixel(i, ((sin(i + Position) * 127 + 128) / 255) * red, + ((sin(i + Position) * 127 + 128) / 255) * green, + ((sin(i + Position) * 127 + 128) / 255) * blue); + } + + FastLED.show(); + if (safeDelay(WaveDelay)) + return; + } +} + +//-------------------------------Sparkle--------------------------------------- +void Sparkle(byte red, byte green, byte blue, int SpeedDelay) +{ + int Pixel = random(LED_COUNT); + setPixel(Pixel, red, green, blue); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + setPixel(Pixel, 0, 0, 0); +} + +//-------------------------------SnowSparkle--------------------------------------- +void SnowSparkle(byte red, byte green, byte blue, int SparkleDelay, int SpeedDelay) +{ + setAll(red, green, blue); + + int Pixel = random(LED_COUNT); + setPixel(Pixel, 0xff, 0xff, 0xff); + FastLED.show(); + if (safeDelay(SparkleDelay)) + return; + setPixel(Pixel, red, green, blue); + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; +} +//?? +//-------------------------------theaterChase--------------------------------------- +void theaterChase(byte red, byte green, byte blue, int SpeedDelay) +{ + for (int j = 0; j < 10; j++) + { // do 10 cycles of chasing + for (int q = 0; q < 3; q++) + { + for (int i = 0; i < LED_COUNT; i = i + 3) + { + setPixel(i + q, red, green, blue); // turn every third pixel on + } + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + for (int i = 0; i < LED_COUNT; i = i + 3) + { + setPixel(i + q, 0, 0, 0); // turn every third pixel off + } + } + } +} + +//-------------------------------theaterChaseRainbow--------------------------------------- +void theaterChaseRainbow(int SpeedDelay) +{ + byte *c; + + for (int j = 0; j < 256; j++) + { // cycle all 256 colors in the wheel + for (int q = 0; q < 3; q++) + { + for (int i = 0; i < LED_COUNT; i = i + 3) + { + c = Wheel((i + j) % 255); + setPixel(i + q, *c, *(c + 1), *(c + 2)); // turn every third pixel on + } + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + for (int i = 0; i < LED_COUNT; i = i + 3) + { + setPixel(i + q, 0, 0, 0); // turn every third pixel off + } + } + } +} +//?? +//-------------------------------Strobe--------------------------------------- +void Strobe(byte red, byte green, byte blue, int StrobeCount, int FlashDelay, int EndPause) +{ + for (int j = 0; j < StrobeCount; j++) + { + setAll(red, green, blue); + FastLED.show(); + if (safeDelay(FlashDelay)) + return; + setAll(0, 0, 0); + FastLED.show(); + if (safeDelay(FlashDelay)) + return; + } + + if (safeDelay(EndPause)) + return; +} + +//-------------------------------BouncingBalls--------------------------------------- +void BouncingBalls(byte red, byte green, byte blue, int BallCount) +{ + float Gravity = -9.81; + int StartHeight = 1; + + float Height[BallCount]; + float ImpactVelocityStart = sqrt(-2 * Gravity * StartHeight); + float ImpactVelocity[BallCount]; + float TimeSinceLastBounce[BallCount]; + int Position[BallCount]; + long ClockTimeSinceLastBounce[BallCount]; + float Dampening[BallCount]; + + for (int i = 0; i < BallCount; i++) + { + ClockTimeSinceLastBounce[i] = millis(); + Height[i] = StartHeight; + Position[i] = 0; + ImpactVelocity[i] = ImpactVelocityStart; + TimeSinceLastBounce[i] = 0; + Dampening[i] = 0.90 - float(i) / pow(BallCount, 2); + } + + while (true) + { + if (changeFlag) + { + changeFlag = false; + return; + } + for (int i = 0; i < BallCount; i++) + { + TimeSinceLastBounce[i] = millis() - ClockTimeSinceLastBounce[i]; + Height[i] = 0.5 * Gravity * pow(TimeSinceLastBounce[i] / 1000, 2.0) + ImpactVelocity[i] * TimeSinceLastBounce[i] / 1000; + + if (Height[i] < 0) + { + Height[i] = 0; + ImpactVelocity[i] = Dampening[i] * ImpactVelocity[i]; + ClockTimeSinceLastBounce[i] = millis(); + + if (ImpactVelocity[i] < 0.01) + { + ImpactVelocity[i] = ImpactVelocityStart; + } + } + Position[i] = round(Height[i] * (LED_COUNT - 1) / StartHeight); + } + + for (int i = 0; i < BallCount; i++) + { + setPixel(Position[i], red, green, blue); + } + FastLED.show(); + setAll(0, 0, 0); + } +} + +//-------------------------------BouncingColoredBalls--------------------------------------- +void BouncingColoredBalls(int BallCount, byte colors[][3]) +{ + float Gravity = -9.81; + int StartHeight = 1; + + float Height[BallCount]; + float ImpactVelocityStart = sqrt(-2 * Gravity * StartHeight); + float ImpactVelocity[BallCount]; + float TimeSinceLastBounce[BallCount]; + int Position[BallCount]; + long ClockTimeSinceLastBounce[BallCount]; + float Dampening[BallCount]; + + for (int i = 0; i < BallCount; i++) + { + ClockTimeSinceLastBounce[i] = millis(); + Height[i] = StartHeight; + Position[i] = 0; + ImpactVelocity[i] = ImpactVelocityStart; + TimeSinceLastBounce[i] = 0; + Dampening[i] = 0.90 - float(i) / pow(BallCount, 2); + } + + while (true) + { + if (changeFlag) + { + changeFlag = false; + return; + } + for (int i = 0; i < BallCount; i++) + { + TimeSinceLastBounce[i] = millis() - ClockTimeSinceLastBounce[i]; + Height[i] = 0.5 * Gravity * pow(TimeSinceLastBounce[i] / 1000, 2.0) + ImpactVelocity[i] * TimeSinceLastBounce[i] / 1000; + + if (Height[i] < 0) + { + Height[i] = 0; + ImpactVelocity[i] = Dampening[i] * ImpactVelocity[i]; + ClockTimeSinceLastBounce[i] = millis(); + + if (ImpactVelocity[i] < 0.01) + { + ImpactVelocity[i] = ImpactVelocityStart; + } + } + Position[i] = round(Height[i] * (LED_COUNT - 1) / StartHeight); + } + + for (int i = 0; i < BallCount; i++) + { + setPixel(Position[i], colors[i][0], colors[i][1], colors[i][2]); + } + FastLED.show(); + setAll(0, 0, 0); + } +} + +//-----------Meteor Rain------- +void fadeToBlack(int ledNo, byte fadeValue) +{ +#ifdef ADAFRUIT_NEOPIXEL_H + // NeoPixel + uint32_t oldColor; + uint8_t r, g, b; + int value; + + oldColor = strip.getPixelColor(ledNo); + r = (oldColor & 0x00ff0000UL) >> 16; + g = (oldColor & 0x0000ff00UL) >> 8; + b = (oldColor & 0x000000ffUL); + + r = (r <= 10) ? 0 : (int)r - (r * fadeValue / 256); + g = (g <= 10) ? 0 : (int)g - (g * fadeValue / 256); + b = (b <= 10) ? 0 : (int)b - (b * fadeValue / 256); + + strip.setPixelColor(ledNo, r, g, b); +#endif +#ifndef ADAFRUIT_NEOPIXEL_H + // FastLED + leds[ledNo].fadeToBlackBy(fadeValue); +#endif +} +void meteorRain(byte red, byte green, byte blue, byte meteorSize, byte meteorTrailDecay, boolean meteorRandomDecay, int SpeedDelay) +{ + setAll(0, 0, 0); + + for (int i = 0; i < NUM_LEDS + NUM_LEDS; i++) + { + + // fade brightness all LEDs one step + for (int j = 0; j < NUM_LEDS; j++) + { + if ((!meteorRandomDecay) || (random(10) > 5)) + { + fadeToBlack(j, meteorTrailDecay); + } + } + + // draw meteor + for (int j = 0; j < meteorSize; j++) + { + if ((i - j < NUM_LEDS) && (i - j >= 0)) + { + setPixel(i - j, red, green, blue); + } + } + + FastLED.show(); + if (safeDelay(SpeedDelay)) + return; + } +} diff --git a/src/modules/display/GyverLAMP/matrix.h b/src/modules/display/GyverLAMP/matrix.h new file mode 100644 index 00000000..ce102bf1 --- /dev/null +++ b/src/modules/display/GyverLAMP/matrix.h @@ -0,0 +1,770 @@ +#include "utility.h" + +byte hue; +boolean loadingFlag = true; +// **************** НАСТРОЙКИ ЭФФЕКТОВ **************** +// эффект "синусоиды" - ОТКЛЮЧЕН +#define WAVES_AMOUNT 2 // количество синусоид + +// эффект "шарики" +#define BALLS_AMOUNT 3 // количество "шариков" +#define CLEAR_PATH 1 // очищать путь +#define BALL_TRACK 1 // (0 / 1) - вкл/выкл следы шариков +#define DRAW_WALLS 0 // режим с рисованием препятствий для шаров (не работает на ESP и STM32) +#define TRACK_STEP 70 // длина хвоста шарика (чем больше цифра, тем хвост короче) + +// эффект "квадратик" +#define BALL_SIZE 3 // размер шара +#define RANDOM_COLOR 1 // случайный цвет при отскоке + +// эффект "огонь" +#define SPARKLES 1 // вылетающие угольки вкл выкл +#define HUE_ADD 0 // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени + +// эффект "кометы" +#define TAIL_STEP 100 // длина хвоста кометы +#define SATURATION 150 // насыщенность кометы (от 0 до 255) +#define STAR_DENSE 60 // количество (шанс появления) комет + +// эффект "конфетти" +#define DENSE 3 // плотность конфетти +#define BRIGHT_STEP 70 // шаг уменьшения яркости + +// эффект "снег" +#define SNOW_DENSE 10 // плотность снегопада + +// эффект "Светляки" +#define LIGHTERS_AM 35 // количество светляков + +uint32_t globalColor = 0xffffff; // Цвет рисования при запуске белый +unsigned char matrixValue[8][16]; +unsigned char line[WIDTH]; +int pcnt = 0; +int effectSpeed = _speed; // скрость изменения эффекта +uint8_t USE_SEGMENTS = 1; +uint8_t BorderWidth = 0; +uint8_t dir_mx, seg_num, seg_size, seg_offset; +uint16_t XY(uint8_t, uint8_t); // __attribute__ ((weak)); + +// эффекты матрицы + +// *********** "дыхание" яркостью *********** +boolean brightnessDirection; +byte breathBrightness; // Яркость эффекта "Дыхание" +byte globalBrightness = _brightness; +void brightnessRoutine() +{ + if (brightnessDirection) + { + breathBrightness += 2; + if (breathBrightness > globalBrightness - 1) + { + brightnessDirection = false; + } + } + else + { + breathBrightness -= 2; + if (breathBrightness < 1) + { + brightnessDirection = true; + } + } + FastLED.setBrightness(breathBrightness); +} + +// *********** снегопад 2.0 *********** +void snowRoutine() +{ + + // сдвигаем всё вниз + for (byte x = 0; x < WIDTH; x++) + { + for (byte y = 0; y < HEIGHT - 1; y++) + { + drawPixelXY(x, y, getPixColorXY(x, y + 1)); + } + } + + for (byte x = 0; x < WIDTH; x++) + { + // заполняем случайно верхнюю строку + // а также не даём двум блокам по вертикали вместе быть + if (getPixColorXY(x, HEIGHT - 2) == 0 && (random(0, SNOW_DENSE) == 0)) + drawPixelXY(x, HEIGHT - 1, 0xE0FFFF - 0x101010 * random(0, 4)); + else + drawPixelXY(x, HEIGHT - 1, 0x000000); + } +} + +// ***************************** БЛУДНЫЙ КУБИК ***************************** +int coordB[2]; +int8_t vectorB[2]; +CRGB ballColor; + +void ballRoutine() +{ + if (loadingFlag) + { + for (byte i = 0; i < 2; i++) + { + coordB[i] = WIDTH / 2 * 10; + vectorB[i] = random(8, 20); + ballColor = CHSV(random(0, 9) * 28, 255, 255); + } + + loadingFlag = false; + } + for (byte i = 0; i < 2; i++) + { + coordB[i] += vectorB[i]; + if (coordB[i] < 0) + { + coordB[i] = 0; + vectorB[i] = -vectorB[i]; + if (RANDOM_COLOR) + ballColor = CHSV(random(0, 9) * 28, 255, 255); + // vectorB[i] += random(0, 6) - 3; + } + } + if (coordB[0] > (WIDTH - BALL_SIZE) * 10) + { + coordB[0] = (WIDTH - BALL_SIZE) * 10; + vectorB[0] = -vectorB[0]; + if (RANDOM_COLOR) + ballColor = CHSV(random(0, 9) * 28, 255, 255); + // vectorB[0] += random(0, 6) - 3; + } + if (coordB[1] > (HEIGHT - BALL_SIZE) * 10) + { + coordB[1] = (HEIGHT - BALL_SIZE) * 10; + vectorB[1] = -vectorB[1]; + if (RANDOM_COLOR) + ballColor = CHSV(random(0, 9) * 28, 255, 255); + // vectorB[1] += random(0, 6) - 3; + } + FastLED.clear(); + for (byte i = 0; i < BALL_SIZE; i++) + for (byte j = 0; j < BALL_SIZE; j++) + leds[getPixelNumber(coordB[0] / 10 + i, coordB[1] / 10 + j)] = ballColor; +} + +// *********** радуга заливка *********** +void rainbowRoutine() +{ + + hue += 3; + for (byte i = 0; i < WIDTH; i++) + { + CHSV thisColor = CHSV((byte)(hue + i * float(255 / WIDTH)), 255, 255); + for (byte j = 0; j < HEIGHT; j++) + drawPixelXY(i, j, thisColor); // leds[getPixelNumber(i, j)] = thisColor; + } +} + +// *********** радуга дигональная *********** +void rainbowDiagonalRoutine() +{ + + hue += 3; + for (byte x = 0; x < WIDTH; x++) + { + for (byte y = 0; y < HEIGHT; y++) + { + CHSV thisColor = CHSV((byte)(hue + (float)(WIDTH / HEIGHT * x + y) * (float)(255 / 100)), 255, 255); + drawPixelXY(x, y, thisColor); // leds[getPixelNumber(i, j)] = thisColor; + } + } +} + +// *********** радуга активных светодиодов (рисунка) *********** +void rainbowColorsRoutine() +{ + hue++; + for (byte i = 0; i < WIDTH; i++) + { + CHSV thisColor = CHSV((byte)(hue + i * float(255 / WIDTH)), 255, 255); + for (byte j = 0; j < HEIGHT; j++) + if (getPixColor(getPixelNumber(i, j)) > 0) + drawPixelXY(i, j, thisColor); + } +} + +// ****************************** ОГОНЬ ****************************** +// ********************** огонь ********************** +// these values are substracetd from the generated values to give a shape to the animation +const unsigned char valueMask[8][16] PROGMEM = { + {32, 0, 0, 0, 0, 0, 0, 32, 32, 0, 0, 0, 0, 0, 0, 32}, + {64, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 64}, + {96, 32, 0, 0, 0, 0, 32, 96, 96, 32, 0, 0, 0, 0, 32, 96}, + {128, 64, 32, 0, 0, 32, 64, 128, 128, 64, 32, 0, 0, 32, 64, 128}, + {160, 96, 64, 32, 32, 64, 96, 160, 160, 96, 64, 32, 32, 64, 96, 160}, + {192, 128, 96, 64, 64, 96, 128, 192, 192, 128, 96, 64, 64, 96, 128, 192}, + {255, 160, 128, 96, 96, 128, 160, 255, 255, 160, 128, 96, 96, 128, 160, 255}, + {255, 192, 160, 128, 128, 160, 192, 255, 255, 192, 160, 128, 128, 160, 192, 255}}; + +// these are the hues for the fire, +// should be between 0 (red) to about 25 (yellow) +const unsigned char hueMask[8][16] PROGMEM = { + {1, 11, 19, 25, 25, 22, 11, 1, 1, 11, 19, 25, 25, 22, 11, 1}, + {1, 8, 13, 19, 25, 19, 8, 1, 1, 8, 13, 19, 25, 19, 8, 1}, + {1, 8, 13, 16, 19, 16, 8, 1, 1, 8, 13, 16, 19, 16, 8, 1}, + {1, 5, 11, 13, 13, 13, 5, 1, 1, 5, 11, 13, 13, 13, 5, 1}, + {1, 5, 11, 11, 11, 11, 5, 1, 1, 5, 11, 11, 11, 11, 5, 1}, + {0, 1, 5, 8, 8, 5, 1, 0, 0, 1, 5, 8, 8, 5, 1, 0}, + {0, 0, 1, 5, 5, 1, 0, 0, 0, 0, 1, 5, 5, 1, 0, 0}, + {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0}}; +// Randomly generate the next line (matrix row) + +void generateLine() +{ + for (uint8_t x = 0; x < WIDTH; x++) + { + line[x] = random(64, 255); + } +} + +// shift all values in the matrix up one row + +void shiftUp() +{ + for (uint8_t y = HEIGHT - 1; y > 0; y--) + { + for (uint8_t x = 0; x < WIDTH; x++) + { + uint8_t newX = x; + if (x > 15) + newX = x % 16; + if (y > 7) + continue; + matrixValue[y][newX] = matrixValue[y - 1][newX]; + } + } + + for (uint8_t x = 0; x < WIDTH; x++) + { + uint8_t newX = x; + if (x > 15) + newX = x % 16; + matrixValue[0][newX] = line[newX]; + } +} + +// draw a frame, interpolating between 2 "key frames" +// @param pcnt percentage of interpolation + +void drawFrame(int pcnt) +{ + int nextv; + + // each row interpolates with the one before it + for (unsigned char y = HEIGHT - 1; y > 0; y--) + { + for (unsigned char x = 0; x < WIDTH; x++) + { + uint8_t newX = x; + if (x > 15) + newX = x % 16; + if (y < 8) + { + nextv = + (((100.0 - pcnt) * matrixValue[y][newX] + pcnt * matrixValue[y - 1][newX]) / 100.0) - pgm_read_byte(&(valueMask[y][newX])); + + CRGB color = CHSV( + HUE_ADD + pgm_read_byte(&(hueMask[y][newX])), // H + 255, // S + (uint8_t)max(0, nextv) // V + ); + + leds[getPixelNumber(x, y)] = color; + } + else if (y == 8 && SPARKLES) + { + if (random(0, 20) == 0 && getPixColorXY(x, y - 1) != 0) + drawPixelXY(x, y, getPixColorXY(x, y - 1)); + else + drawPixelXY(x, y, 0); + } + else if (SPARKLES) + { + + // старая версия для яркости + if (getPixColorXY(x, y - 1) > 0) + drawPixelXY(x, y, getPixColorXY(x, y - 1)); + else + drawPixelXY(x, y, 0); + } + } + } + + // first row interpolates with the "next" line + for (unsigned char x = 0; x < WIDTH; x++) + { + uint8_t newX = x; + if (x > 15) + newX = x % 16; + CRGB color = CHSV( + HUE_ADD + pgm_read_byte(&(hueMask[0][newX])), // H + 255, // S + (uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V + ); + // leds[getPixelNumber(newX, 0)] = color; // На форуме пишут что это ошибка - вместо newX должно быть x, иначе + leds[getPixelNumber(x, 0)] = color; // на матрицах шире 16 столбцов нижний правый угол неработает + } +} +void fireRoutine() +{ + if (loadingFlag) + { + + loadingFlag = false; + FastLED.clear(); + generateLine(); + memset(matrixValue, 0, sizeof(matrixValue)); + } + if (pcnt >= 100) + { + shiftUp(); + generateLine(); + pcnt = 0; + } + drawFrame(pcnt); + pcnt += 30; +} + +// **************** МАТРИЦА ***************** +void matrixRoutine() +{ + if (loadingFlag) + { + loadingFlag = false; + + FastLED.clear(); + } + for (byte x = 0; x < WIDTH; x++) + { + // заполняем случайно верхнюю строку + uint32_t thisColor = getPixColorXY(x, HEIGHT - 1); + if (thisColor == 0) + drawPixelXY(x, HEIGHT - 1, 0x00FF00 * (random(0, 10) == 0)); + else if (thisColor < 0x002000) + drawPixelXY(x, HEIGHT - 1, 0); + else + drawPixelXY(x, HEIGHT - 1, thisColor - 0x002000); + } + + // сдвигаем всё вниз + for (byte x = 0; x < WIDTH; x++) + { + for (byte y = 0; y < HEIGHT - 1; y++) + { + drawPixelXY(x, y, getPixColorXY(x, y + 1)); + } + } +} + +// ********************* ЗВЕЗДОПАД ****************** +void fadePixel(byte i, byte j, byte step) +{ // новый фейдер + int pixelNum = getPixelNumber(i, j); + if (getPixColor(pixelNum) == 0) + return; + + if (leds[pixelNum].r >= 30 || + leds[pixelNum].g >= 30 || + leds[pixelNum].b >= 30) + { + leds[pixelNum].fadeToBlackBy(step); + } + else + { + leds[pixelNum] = 0; + } +} +// функция плавного угасания цвета для всех пикселей +void fader(byte step) +{ + for (byte i = 0; i < WIDTH; i++) + { + for (byte j = 0; j < HEIGHT; j++) + { + fadePixel(i, j, step); + } + } +} + +void starfallRoutine() +{ + + // заполняем головами комет левую и верхнюю линию + for (byte i = HEIGHT / 2; i < HEIGHT; i++) + { + if (getPixColorXY(0, i) == 0 && (random(0, STAR_DENSE) == 0) && getPixColorXY(0, i + 1) == 0 && getPixColorXY(0, i - 1) == 0) + leds[getPixelNumber(0, i)] = CHSV(random(0, 200), SATURATION, 255); + } + for (byte i = 0; i < WIDTH / 2; i++) + { + if (getPixColorXY(i, HEIGHT - 1) == 0 && (random(0, STAR_DENSE) == 0) && getPixColorXY(i + 1, HEIGHT - 1) == 0 && getPixColorXY(i - 1, HEIGHT - 1) == 0) + leds[getPixelNumber(i, HEIGHT - 1)] = CHSV(random(0, 200), SATURATION, 255); + } + + // сдвигаем по диагонали + for (byte y = 0; y < HEIGHT - 1; y++) + { + for (byte x = WIDTH - 1; x > 0; x--) + { + drawPixelXY(x, y, getPixColorXY(x - 1, y + 1)); + } + } + + // уменьшаем яркость левой и верхней линии, формируем "хвосты" + for (byte i = HEIGHT / 2; i < HEIGHT; i++) + { + fadePixel(0, i, TAIL_STEP); + } + for (byte i = 0; i < WIDTH / 2; i++) + { + fadePixel(i, HEIGHT - 1, TAIL_STEP); + } +} + +// рандомные гаснущие вспышки +void sparklesRoutine() +{ + + for (byte i = 0; i < DENSE; i++) + { + byte x = random(0, WIDTH); + byte y = random(0, HEIGHT); + if (getPixColorXY(x, y) == 0) + leds[getPixelNumber(x, y)] = CHSV(random(0, 255), 255, 255); + } + fader(BRIGHT_STEP); +} + +// ----------------------------- СВЕТЛЯКИ ------------------------------ +int lightersPos[2][LIGHTERS_AM]; +int8_t lightersSpeed[2][LIGHTERS_AM]; +CHSV lightersColor[LIGHTERS_AM]; +byte loopCounter; + +int angle[LIGHTERS_AM]; +int speedV[LIGHTERS_AM]; +int8_t angleSpeed[LIGHTERS_AM]; + +void lightersRoutine() +{ + if (loadingFlag) + { + loadingFlag = false; + randomSeed(millis()); + for (byte i = 0; i < LIGHTERS_AM; i++) + { + lightersPos[0][i] = random(0, WIDTH * 10); + lightersPos[1][i] = random(0, HEIGHT * 10); + lightersSpeed[0][i] = random(-10, 10); + lightersSpeed[1][i] = random(-10, 10); + lightersColor[i] = CHSV(random(0, 255), 255, 255); + } + } + FastLED.clear(); + if (++loopCounter > 20) + loopCounter = 0; + for (byte i = 0; i < map(LIGHTERS_AM, 0, 255, 5, 150); i++) + { + if (loopCounter == 0) + { // меняем скорость каждые 255 отрисовок + lightersSpeed[0][i] += random(-3, 4); + lightersSpeed[1][i] += random(-3, 4); + lightersSpeed[0][i] = constrain(lightersSpeed[0][i], -20, 20); + lightersSpeed[1][i] = constrain(lightersSpeed[1][i], -20, 20); + } + + lightersPos[0][i] += lightersSpeed[0][i]; + lightersPos[1][i] += lightersSpeed[1][i]; + + if (lightersPos[0][i] < 0) + lightersPos[0][i] = (WIDTH - 1) * 10; + if (lightersPos[0][i] >= WIDTH * 10) + lightersPos[0][i] = 0; + + if (lightersPos[1][i] < 0) + { + lightersPos[1][i] = 0; + lightersSpeed[1][i] = -lightersSpeed[1][i]; + } + if (lightersPos[1][i] >= (HEIGHT - 1) * 10) + { + lightersPos[1][i] = (HEIGHT - 1) * 10; + lightersSpeed[1][i] = -lightersSpeed[1][i]; + } + drawPixelXY(lightersPos[0][i] / 10, lightersPos[1][i] / 10, lightersColor[i]); + } +} + +// ------------- ПЕЙНТБОЛ ------------- + +void lightBallsRoutine() +{ + if (loadingFlag) + { + loadingFlag = false; + + FastLED.clear(); // очистить + dir_mx = WIDTH > HEIGHT ? 0 : 1; // 0 - квадратные сегменты расположены горизонтально, 1 - вертикально + seg_num = dir_mx == 0 ? (WIDTH / HEIGHT) : (HEIGHT / WIDTH); // вычисляем количество сегментов, умещающихся на матрице + seg_size = dir_mx == 0 ? HEIGHT : WIDTH; // Размер квадратного сегмента (высота и ширина равны) + seg_offset = ((dir_mx == 0 ? WIDTH : HEIGHT) - seg_size * seg_num) / (seg_num + 1); // смещение от края матрицы и между сегментами + BorderWidth = 0; + } + + // Apply some blurring to whatever's already on the matrix + // Note that we never actually clear the matrix, we just constantly + // blur it repeatedly. Since the blurring is 'lossy', there's + // an automatic trend toward black -- by design. + uint8_t blurAmount = dim8_raw(beatsin8(2, 64, 100)); + blur2d(leds, WIDTH, HEIGHT, blurAmount); + + // The color of each point shifts over time, each at a different speed. + uint32_t ms = millis(); + int16_t idx; + + byte cnt = map(effectSpeed, 0, 255, 1, 4); + + if (USE_SEGMENTS != 0) + { + // Для неквадратных - вычленяем квадратные сегменты, которые равномерно распределяем по ширине / высоте матрицы + uint8_t i = beatsin8(91, 0, seg_size - BorderWidth - 1); + uint8_t j = beatsin8(109, 0, seg_size - BorderWidth - 1); + uint8_t k = beatsin8(73, 0, seg_size - BorderWidth - 1); + uint8_t m = beatsin8(123, 0, seg_size - BorderWidth - 1); + + uint8_t d1 = ms / 29; + uint8_t d2 = ms / 41; + uint8_t d3 = ms / 73; + uint8_t d4 = ms / 97; + + for (uint8_t ii = 0; ii < seg_num; ii++) + { + delay(0); // Для предотвращения ESP8266 Watchdog Timer + uint8_t cx = dir_mx == 0 ? (seg_offset * (ii + 1) + seg_size * ii) : 0; + uint8_t cy = dir_mx == 0 ? 0 : (seg_offset * (ii + 1) + seg_size * ii); + uint8_t color_shift = ii * 50; + if (cnt <= 1) + { + idx = XY(i + cx, j + cy); + leds[idx] += CHSV(color_shift + d1, 200U, 255U); + } + if (cnt <= 2) + { + idx = XY(j + cx, k + cy); + leds[idx] += CHSV(color_shift + d2, 200U, 255U); + } + if (cnt <= 3) + { + idx = XY(k + cx, m + cy); + leds[idx] += CHSV(color_shift + d3, 200U, 255U); + } + if (cnt <= 4) + { + idx = XY(m + cx, i + cy); + leds[idx] += CHSV(color_shift + d4, 200U, 255U); + } + + // При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые + // не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно. + // При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается + for (byte i2 = cy; i2 < cy + seg_size; i2++) + { + fadePixel(cx + BorderWidth, i2, 15); + fadePixel(cx + seg_size - BorderWidth - 1, i2, 15); + } + } + } + else + { + uint8_t i = beatsin8(91, BorderWidth, WIDTH - BorderWidth - 1); + uint8_t j = beatsin8(109, BorderWidth, HEIGHT - BorderWidth - 1); + uint8_t k = beatsin8(73, BorderWidth, WIDTH - BorderWidth - 1); + uint8_t m = beatsin8(123, BorderWidth, HEIGHT - BorderWidth - 1); + + if (cnt <= 1) + { + idx = XY(i, j); + leds[idx] += CHSV(ms / 29, 200U, 255U); + } + if (cnt <= 2) + { + idx = XY(k, j); + leds[idx] += CHSV(ms / 41, 200U, 255U); + } + if (cnt <= 3) + { + idx = XY(k, m); + leds[idx] += CHSV(ms / 73, 200U, 255U); + } + if (cnt <= 4) + { + idx = XY(i, m); + leds[idx] += CHSV(ms / 97, 200U, 255U); + } + + if (WIDTH == HEIGHT) + { + // При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые + // не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно. + // При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается + for (byte i = 0; i < HEIGHT; i++) + { + fadePixel(0, i, 15); + fadePixel(WIDTH - 1, i, 15); + } + } + } +} +// ------------- ВОДОВОРОТ ------------- + +void swirlRoutine() +{ + if (loadingFlag) + { + loadingFlag = false; + + FastLED.clear(); // очистить + dir_mx = WIDTH > HEIGHT ? 0 : 1; // 0 - квадратные сегменты расположены горизонтально, 1 - вертикально + seg_num = dir_mx == 0 ? (WIDTH / HEIGHT) : (HEIGHT / WIDTH); // вычисляем количество сегментов, умещающихся на матрице + seg_size = dir_mx == 0 ? HEIGHT : WIDTH; // Размер квадратного сегмента (высота и ширина равны) + seg_offset = ((dir_mx == 0 ? WIDTH : HEIGHT) - seg_size * seg_num) / (seg_num + 1); // смещение от края матрицы и между сегментами + BorderWidth = seg_num == 1 ? 0 : 1; + } + + // Apply some blurring to whatever's already on the matrix + // Note that we never actually clear the matrix, we just constantly + // blur it repeatedly. Since the blurring is 'lossy', there's + // an automatic trend toward black -- by design. + uint8_t blurAmount = dim8_raw(beatsin8(2, 64, 100)); + blur2d(leds, WIDTH, HEIGHT, blurAmount); + + uint32_t ms = millis(); + int16_t idx; + + if (USE_SEGMENTS != 0) + { + // Use two out-of-sync sine waves + uint8_t i = beatsin8(41, 0, seg_size - BorderWidth - 1); + uint8_t j = beatsin8(27, 0, seg_size - BorderWidth - 1); + + // Also calculate some reflections + uint8_t ni = (seg_size - 1) - i; + uint8_t nj = (seg_size - 1) - j; + + uint8_t d1 = ms / 11; + uint8_t d2 = ms / 13; + uint8_t d3 = ms / 17; + uint8_t d4 = ms / 29; + uint8_t d5 = ms / 37; + uint8_t d6 = ms / 41; + + for (uint8_t ii = 0; ii < seg_num; ii++) + { + delay(0); // Для предотвращения ESP8266 Watchdog Timer + uint8_t cx = dir_mx == 0 ? (seg_offset * (ii + 1) + seg_size * ii) : 0; + uint8_t cy = dir_mx == 0 ? 0 : (seg_offset * (ii + 1) + seg_size * ii); + uint8_t color_shift = ii * 50; + + // The color of each point shifts over time, each at a different speed. + idx = XY(i + cx, j + cy); + leds[idx] += CHSV(color_shift + d1, 200, 192); + idx = XY(ni + cx, nj + cy); + leds[idx] += CHSV(color_shift + d2, 200, 192); + idx = XY(i + cx, nj + cy); + leds[idx] += CHSV(color_shift + d3, 200, 192); + idx = XY(ni + cx, j + cy); + leds[idx] += CHSV(color_shift + d4, 200, 192); + idx = XY(j + cx, i + cy); + leds[idx] += CHSV(color_shift + d5, 200, 192); + idx = XY(nj + cx, ni + cy); + leds[idx] += CHSV(color_shift + d6, 200, 192); + + // При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые + // не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно. + // При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается + for (byte i2 = cy; i2 < cy + seg_size; i2++) + { + fadePixel(cx, i2, 15); + fadePixel(cx + BorderWidth, i2, 15); + fadePixel(cx + seg_size - 1, i2, 15); + fadePixel(cx + seg_size - BorderWidth - 1, i2, 15); + } + } + } + else + { + // Use two out-of-sync sine waves + uint8_t i = beatsin8(41, BorderWidth, WIDTH - BorderWidth - 1); + uint8_t j = beatsin8(27, BorderWidth, HEIGHT - BorderWidth - 1); + + // Also calculate some reflections + uint8_t ni = (WIDTH - 1) - i; + uint8_t nj = (HEIGHT - 1) - j; + + // The color of each point shifts over time, each at a different speed. + idx = XY(i, j); + leds[idx] += CHSV(ms / 11, 200, 192); + idx = XY(ni, nj); + leds[idx] += CHSV(ms / 13, 200, 192); + idx = XY(i, nj); + leds[idx] += CHSV(ms / 17, 200, 192); + idx = XY(ni, j); + leds[idx] += CHSV(ms / 29, 200, 192); + + if (HEIGHT == WIDTH) + { + // для квадратных матриц - 6 точек создают более красивую картину + idx = XY(j, i); + leds[idx] += CHSV(ms / 37, 200, 192); + idx = XY(nj, ni); + leds[idx] += CHSV(ms / 41, 200, 192); + + // При соединении матрицы из угла вверх или вниз почему-то слева и справа узора остаются полосы, которые + // не гаснут обычным blur - гасим полоски левой и правой стороны дополнительно. + // При соединении из угла влево или вправо или на неквадратных матрицах такого эффекта не наблюдается + for (byte i = 0; i < HEIGHT; i++) + { + fadePixel(0, i, 15); + fadePixel(WIDTH - 1, i, 15); + } + } + } +} + +uint16_t XY(uint8_t x, uint8_t y) +{ + return getPixelNumber(x, y); +} + +//--------------------крутящаяся радуга матрица------------------ + +void rainbow_loop_matrix() +{ //-m3-LOOP HSV RAINBOW + idex++; + ihue = ihue + thisstep; + if (idex >= LED_COUNT) + { + idex = 0; + } + if (ihue > 255) + { + ihue = 0; + } + + for (byte i = 0; i < WIDTH; i++) + { + CHSV thisColor = CHSV(ihue, thissat, 255); + for (byte j = 0; j < HEIGHT; j++) + drawPixelXY(i, j, thisColor); // leds[getPixelNumber(i, j)] = thisColor; + } + LEDS.show(); + if (safeDelay(thisdelay)) + return; +} \ No newline at end of file diff --git a/src/modules/display/GyverLAMP/modinfo.json b/src/modules/display/GyverLAMP/modinfo.json new file mode 100644 index 00000000..d5aff0d6 --- /dev/null +++ b/src/modules/display/GyverLAMP/modinfo.json @@ -0,0 +1,40 @@ +{ + "menuSection": "screens", + "configItem": [ + { + "global": 0, + "name": "GyverLAMP", + "type": "Reading", + "subtype": "GyverLAMP", + "id": "GyverLAMP", + "widget": "range", + "page": "Кнопки", + "descr": "GyverLAMP", + "needSave": 0, + "brightness": "10", + "speed": 30, + "dalay": 10000 + } + ], + "about": { + "authorName": "", + "authorContact": "", + "authorGit": "", + "exampleURL": "", + "specialThanks": "", + "moduleName": "GyverLAMP", + "moduleVersion": "1", + "moduleDesc": "xxx", + "propInfo": {}, + "funcInfo": [] + }, + "defActive": false, + "usedLibs": { + "esp32*": [ + "fastled/FastLED@^3.5.0" + ], + "esp82*": [ + "fastled/FastLED@^3.5.0" + ] + } +} \ No newline at end of file diff --git a/src/modules/display/GyverLAMP/utility.h b/src/modules/display/GyverLAMP/utility.h new file mode 100644 index 00000000..fff2d740 --- /dev/null +++ b/src/modules/display/GyverLAMP/utility.h @@ -0,0 +1,92 @@ +#include "config.h" +#define MATRIX_TYPE 0 +// #define NUM_LEDS LED_COUNT + +// **************** НАСТРОЙКА МАТРИЦЫ **************** +#if (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y x + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y (HEIGHT - y - 1) + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 3) +#define _WIDTH HEIGHT +#define THIS_X (HEIGHT - y - 1) +#define THIS_Y x + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 2) +#define _WIDTH WIDTH +#define THIS_X (WIDTH - x - 1) +#define THIS_Y (HEIGHT - y - 1) + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 3) +#define _WIDTH HEIGHT +#define THIS_X (HEIGHT - y - 1) +#define THIS_Y (WIDTH - x - 1) + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 2) +#define _WIDTH WIDTH +#define THIS_X (WIDTH - x - 1) +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y (WIDTH - x - 1) + +#else +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y +#pragma message "Wrong matrix parameters! Set to default" + +#endif + +// получить номер пикселя в ленте по координатам +uint16_t getPixelNumber(int8_t x, int8_t y) +{ + if ((THIS_Y % 2 == 0) || MATRIX_TYPE) + { // если чётная строка + return (THIS_Y * _WIDTH + THIS_X); + } + else + { // если нечётная строка + return (THIS_Y * _WIDTH + _WIDTH - THIS_X - 1); + } +} +// функция отрисовки точки по координатам X Y +void drawPixelXY(int8_t x, int8_t y, CRGB color) +{ + + if (x < 0 || x > WIDTH - 1 || y < 0 || y > HEIGHT - 1) + return; + int thisPixel = getPixelNumber(x, y) * SEGMENTS; + for (byte i = 0; i < SEGMENTS; i++) + { + leds[thisPixel + i] = color; + } + //FastLED.show(); +} +// функция получения цвета пикселя по его номеру +uint32_t getPixColor(int thisSegm) +{ + int thisPixel = thisSegm * SEGMENTS; + if (thisPixel < 0 || thisPixel > NUM_LEDS - 1) + return 0; + return (((uint32_t)leds[thisPixel].r << 16) | ((long)leds[thisPixel].g << 8) | (long)leds[thisPixel].b); +} + +// функция получения цвета пикселя в матрице по его координатам +uint32_t getPixColorXY(int8_t x, int8_t y) +{ + return getPixColor(getPixelNumber(x, y)); +} diff --git a/src/modules/display/Lcd2004/modinfo.json b/src/modules/display/Lcd2004/modinfo.json index 0700b92f..bdf3f34f 100644 --- a/src/modules/display/Lcd2004/modinfo.json +++ b/src/modules/display/Lcd2004/modinfo.json @@ -117,7 +117,7 @@ } ] }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "https://github.com/robotclass/RobotClass_LiquidCrystal_I2C", diff --git a/src/modules/display/LedFX/LedFX.cpp b/src/modules/display/LedFX/LedFX.cpp new file mode 100644 index 00000000..b692c811 --- /dev/null +++ b/src/modules/display/LedFX/LedFX.cpp @@ -0,0 +1,332 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPConfiguration.h" +#include + + + +WS2812FX *_glob_strip = nullptr; // глобальный указатель на WS2812FX для использования в функциях для кастомных эффектов +std::vector vuMeterBands; // массив указателей на элементы IoTValue, которые будут использоваться для передачи данных в эффект VU Meter + +uint16_t vuMeter2(void) { +// функция взята из WS2812FX.cpp для демонстрации возможности создания своего алгоритма эффекта +// в данном случае - VU Meter имеет тот же смысл отображения уровня сигнала, что и в WS2812FX с сохранением алгоритма вывода данных из нескольких источников +// но вместо использования внешнего источника данных для встроенного эффекта, мы используем данные из других элементов конфигурации IoTM +// Если данные не поступают (IoTValue.valS == "1"), то используется генерация случайных чисел для демонстрации работы эффекта + + if (_glob_strip == nullptr) return 0; // Проверяем, инициализирована ли библиотека WS2812FX + + uint16_t numBands = vuMeterBands.size(); // Получаем количество полос VU Meter из массива указателей + if (numBands == 0) return 0; // Если нет полос, выходим из функции + + WS2812FX::Segment* seg = _glob_strip->getSegment(); + uint16_t seglen = seg->stop - seg->start + 1; + uint16_t bandSize = seglen / numBands; + + if (vuMeterBands[0]->valS == "R") + for (uint8_t i=0; i < numBands; i++) { + int randomData = vuMeterBands[i]->valD + _glob_strip->random8(32) - _glob_strip->random8(32); + vuMeterBands[i]->valD = (randomData < 0 || randomData > 255) ? 128 : randomData; + } + + for(uint8_t i=0; ivalD)); + uint8_t scaledBand = (vuMeterBands[i]->valD * bandSize) / 256; + for(uint16_t j=0; jstart + (i * bandSize) + j; + if(j <= scaledBand) { + if(j < bandSize - 4) _glob_strip->setPixelColor(index, GREEN); + else if(j < bandSize - 2) _glob_strip->setPixelColor(index, YELLOW); + else _glob_strip->setPixelColor(index, RED); + } else { + _glob_strip->setPixelColor(index, BLACK); + } + } + } + _glob_strip->setCycle(); + + return seg->speed; +} + + +class LedFX : public IoTItem +{ +private: + WS2812FX *_strip; + + int _data_pin = 2; + int _numLeds = 1; + int _brightness = 100; + int _speed = 15000; + int _effectsMode = 0; + int _valueMode = 0; + int _color = 0xFF0000; // default color red + + uint8_t _BrightnessFadeOutStep = 0; + uint8_t _BrightnessFadeOutMin = 1; + uint8_t _BrightnessFadeInStep = 0; + uint8_t _BrightnessFadeInMax = 50; + +public: + LedFX(String parameters) : IoTItem(parameters) { + jsonRead(parameters, F("data_pin"), _data_pin); + jsonRead(parameters, F("speed"), _speed); + jsonRead(parameters, F("numLeds"), _numLeds); + jsonRead(parameters, F("brightness"), _brightness); + + String tmpStr; + jsonRead(parameters, F("color"), tmpStr); + _color = hexStringToUint32(tmpStr); + + jsonRead(parameters, F("effectsMode"), _effectsMode); + jsonRead(parameters, F("valueMode"), _valueMode); + + + //_strip = new WS2812FX(_numLeds, _data_pin, NEO_BRG + NEO_KHZ400); // SM16703 + _strip = new WS2812FX(_numLeds, _data_pin, NEO_GRB + NEO_KHZ800); // WS2812B + + if (_strip != nullptr) { + _glob_strip = _strip; // Сохраняем указатель в глобальной переменной + + _strip->init(); + _strip->setBrightness(_brightness); + _strip->setSpeed(_speed); + _strip->setMode(_effectsMode); + _strip->setColor(_color); + if (_effectsMode >= 0) _strip->start(); + } + } + + void fadeOutNonBlocking() { + + } + + void loop() { + if (!_strip) return; + + static unsigned long lastUpdate = 0; // Время последнего обновления + unsigned long now = millis(); + if (now - lastUpdate >= 70) { // Проверяем, прошло ли достаточно времени + lastUpdate = now; + + if (_BrightnessFadeOutStep > 0) { + int currentBrightness = _strip->getBrightness(); // Получаем текущую яркость + currentBrightness -= _BrightnessFadeOutStep; + if (currentBrightness < _BrightnessFadeOutMin) { + currentBrightness = _BrightnessFadeOutMin; // Убедимся, что яркость не уйдет в отрицательные значения + _BrightnessFadeOutStep = 0; // Останавливаем затухание + } + _strip->setBrightness(currentBrightness); + _strip->show(); + } + + if (_BrightnessFadeInStep > 0) { + int currentBrightness = _strip->getBrightness(); // Получаем текущую яркость + currentBrightness += _BrightnessFadeInStep; + if (currentBrightness > _BrightnessFadeInMax) { + currentBrightness = _BrightnessFadeInMax; // Убедимся, что яркость не уйдет за пределы + _BrightnessFadeInStep = 0; // Останавливаем затухание + } + _strip->setBrightness(currentBrightness); + _strip->show(); + } + } + + _strip->service(); + IoTItem::loop(); + } + + void doByInterval() { + + } + + IoTValue execute(String command, std::vector ¶m) { + if (!_strip) return {}; + if (command == "fadeOut") { + if (param.size() == 2) { + _BrightnessFadeOutMin = param[0].valD; + _BrightnessFadeOutStep = param[1].valD; + SerialPrint("E", "Strip LedFX", "BrightnessFadeOut"); + } + + } else if (command == "fadeIn") { + if (param.size() == 2) { + _BrightnessFadeInMax = param[0].valD; + _BrightnessFadeInStep = param[1].valD; + SerialPrint("E", "Strip LedFX", "BrightnessFadeIn"); + } + + } else if (command == "setColor") { + if (param.size() == 1) { + _color = hexStringToUint32(param[0].valS); + _strip->setColor(_color); + _strip->show(); + SerialPrint("E", "Strip LedFX", "setColor:" + param[0].valS); + } + + } else if (command == "setEffect") { + if (param.size() == 1) { + if (param[0].valD < 0 || param[0].valD > 79) + _effectsMode = random(0, 79); + else + _effectsMode = param[0].valD; + _strip->setMode(_effectsMode); + _strip->show(); + _strip->start(); + SerialPrint("E", "Strip LedFX", "setEffect:" + param[0].valS); + } + + } else if (command == "setSpeed") { + if (param.size() == 1) { + _speed = param[0].valD; + _strip->setSpeed(_speed); + _strip->show(); + SerialPrint("E", "Strip LedFX", "setSpeed:" + param[0].valS); + } + + } else if (command == "setBrightness") { + if (param.size() == 1) { + _brightness = param[0].valD; + _strip->setBrightness(_brightness); + _strip->show(); + SerialPrint("E", "Strip LedFX", "setBrightness:" + param[0].valS); + } + + } else if (command == "stop") { + _strip->stop(); + SerialPrint("E", "Strip LedFX", "stop"); + + } else if (command == "start") { + _strip->start(); + SerialPrint("E", "Strip LedFX", "start"); + + } else if (command == "pause") { + _strip->pause(); + SerialPrint("E", "Strip LedFX", "pause"); + + } else if (command == "resume") { + _strip->resume(); + SerialPrint("E", "Strip LedFX", "resume"); + + } else if (command == "setSegment") { + if (param.size() == 6) { + _strip->setSegment(param[0].valD, param[1].valD, param[2].valD, param[3].valD, hexStringToUint32(param[4].valS), param[5].valD); + _strip->show(); + _strip->start(); + SerialPrint("E", "Strip LedFX", "setSegment:" + param[0].valS + " start:" + param[1].valS + " stop:" + param[2].valS + " mode:" + param[3].valS, " color:" + param[4].valS + " speed:" + param[5].valS); + } + + } else if(command == "noShowOne"){ + if (param.size() == 1) { + _strip->setPixelColor(param[0].valD, _strip->Color(0, 0, 0)); + _strip->show(); + SerialPrint("E", "Strip LedFX", "noShowOne"); + } + + } else if (command == "showLed"){ + if (param.size() == 2) { + uint32_t color = hexStringToUint32(param[1].valS); + _strip->setPixelColor(param[0].valD, color); + _strip->show(); + _strip->start(); + SerialPrint("E", "Strip LedFX", "showLed:" + param[0].valS + " color:" + param[1].valS); + } + + } else if (command == "vuMeter") { + if (param.size() == 2) { + int bandCnt = param[0].valD; + if (param[1].valS == "") { + for (int i=0; i < vuMeterBands.size(); i++) { + delete vuMeterBands[i]; + } + vuMeterBands.clear(); + + for (uint8_t i=0; i < bandCnt; i++) { + IoTValue *band = new IoTValue(); // создаем новый элемент IoTValue для полос VU Meter + band->valD = 0; + band->valS = "R"; + vuMeterBands.push_back(band); // добавляем указатель в массив + } + } else { + // Очищаем массив vuMeterBands перед заполнением + vuMeterBands.clear(); + + String id; + String idsStr = param[1].valS; + // Разделяем строку idsStr на идентификаторы, используя запятую как разделитель + while (idsStr.length() > 0) { + // Извлекаем идентификатор до первой запятой + id = selectToMarker(idsStr, ","); + + // Ищем элемент IoTItem по идентификатору + IoTItem* item = findIoTItem(id); + if (item != nullptr) { + // Добавляем указатель на поле value найденного элемента в vuMeterBands + vuMeterBands.push_back(&(item->value)); + SerialPrint("E", "LedFX", "Добавлен элемент в vuMeterBands: " + id); + } else { + SerialPrint("E", "LedFX", "Элемент не найден: " + id); + } + + int8_t oldSize = idsStr.length(); + // Удаляем обработанный идентификатор из строки + idsStr = deleteBeforeDelimiter(idsStr, ","); + if (idsStr.length() == oldSize) { + // Если длина строки не изменилась, значит, больше нет запятых и это был последний идентификатор + break; + } + } + + } + _strip->setCustomMode(vuMeter2); + _strip->setMode(FX_MODE_CUSTOM); + _strip->start(); + SerialPrint("E", "Strip LedFX", "vuMeter bands:" + param[0].valS + " IDs to show:" + param[1].valS); + } + } + + return {}; + } + + void setValue(const IoTValue& Value, bool genEvent = true) { + if (!_strip) return; + + if (_valueMode == 0) { + _strip->setMode(Value.valD); + _effectsMode = Value.valD; + } else if (_valueMode == 1) { + _strip->setBrightness(Value.valD); + _brightness = Value.valD; + } else if (_valueMode == 2) { + _color = hexStringToUint32(Value.valS); + _strip->setColor(_color); + } else if (_valueMode == 3) { + _strip->setSpeed(Value.valD); + _speed = Value.valD; + } + + value = Value; + regEvent(value.valD, "LedFX", false, genEvent); + } + + ~LedFX() { + if (_strip != nullptr) { + delete _strip; + _strip = nullptr; + _glob_strip = nullptr; // Обнуляем глобальный указатель + } + }; +}; + +void *getAPI_LedFX(String subtype, String param) +{ + if (subtype == F("LedFX")) { + return new LedFX(param); + } else { + return nullptr; + } +} + + + + diff --git a/src/modules/display/LedFX/modinfo.json b/src/modules/display/LedFX/modinfo.json new file mode 100644 index 00000000..5c0ad13b --- /dev/null +++ b/src/modules/display/LedFX/modinfo.json @@ -0,0 +1,163 @@ +{ + "menuSection": "screens", + "configItem": [ + { + "global": 0, + "name": "LedFX", + "type": "Reading", + "subtype": "LedFX", + "id": "fl", + "widget": "inputTxt", + "page": "Кнопки", + "descr": "Лента", + "int": 15, + "needSave": 0, + + "data_pin": "2", + "numLeds": "3", + "brightness": "50", + "speed": "3000", + "color": "0xFF0000", + "effectsMode": 0, + "valueMode": 0 + } + ], + "about": { + "authorName": "Ilya Belyakov", + "authorContact": "https://t.me/Biveraxe", + "authorGit": "https://github.com/biveraxe", + "exampleURL": "https://iotmanager.org/wiki", + "specialThanks": "Yuriy Kuneev (https://t.me/Kuneev07)", + "moduleName": "LedFX", + "moduleVersion": "1.0.1", + "moduleDesc": "Позволяет управлять адресными светодиодными лентами WS2812B и аналогичными.", + "propInfo": { + "int": "Период времени в секундах обновления.", + "data_pin": "Пин к которому подключена лента.", + "speed": "Скорость обновления ленты.", + "numLeds": "Количество пикселей в ленте.", + "needSave": "Запись значения элемента в энергонезависимую память", + "brightness": "Яркость ленты можно менять из сценария.", + "color": "Цвет ленты в формате 0xRRGGBB, например, 0xFF0000 - красный, 0x00FF00 - зеленый, 0x0000FF - синий.", + "effectsMode": "Режим эффектов ленты. 0-79. 0 - Статичный цвет.", + "valueMode": "Режим применения значения элемета. 0 - установка режима эффектов, 1 - регулирование яркости, 2 - изменение цвета, 3 - изменение скорости." + }, + "title": "Адресная светодиодная лента", + "funcInfo": [ + { + "name": "noShowOne", + "descr": "Выключить один светодиод на ленте", + "params": [ + "номер пикселя" + ] + }, + { + "name": "showLed", + "descr": "Зажечь один диод", + "params": [ + "номер пикселя", + "цвет в формате 0xRRGGBB" + ] + }, + { + "name": "setBrightness", + "descr": "Устанавливает общую яркость ленты от 0 до 255", + "params": [ + "яркость от 0 до 255" + ] + }, + { + "name": "vuMeter", + "descr": "Включает режим VU Meter. Важно что бы элемент ленты был ниже в списке чем элемент с датчиком, ИД которого нужно будет мониторить.", + "params": [ + "Количество каналов для отображения на ленте", + "Список ID датчиков через запятую (если указать пустую строку, то каналы будут заполняться случайными числами от 0 до 255)" + ] + }, + { + "name": "setColor", + "descr": "Устанавливает цвет ленты в формате 0xRRGGBB, например, 0xFF0000 - красный, 0x00FF00 - зеленый, 0x0000FF - синий.", + "params": [ + "цвет в формате 0xRRGGBB" + ] + }, + { + "name": "setEffect", + "descr": "Устанавливает эффект ленты. 0-79. 0 - Статичный цвет.", + "params": [ + "номер эффекта от 0 до 79" + ] + }, + { + "name": "setSpeed", + "descr": "Устанавливает скорость эффекта от 0 до 255", + "params": [ + "скорость от 0 до 255" + ] + }, + { + "name": "fadeOut", + "descr": "Плавное затухание яркости", + "params": [ + "Целевое значение яркости, до которого будет затухать", + "Шаг затухания" + ] + }, + { + "name": "fadeIn", + "descr": "Плавное нарастание яркости", + "params": [ + "Целевое значение яркости, до которого будет нарастать", + "Шаг нарастания" + ] + }, + { + "name": "stop", + "descr": "Останавливает эффект", + "params": [] + + }, + { + "name": "start", + "descr": "Запускает эффект", + "params": [] + + }, + { + "name": "pause", + "descr": "Пауза эффекта", + "params": [] + + }, + { + "name": "resume", + "descr": "Возобновляет эффект", + "params": [] + + }, + { + "name": "setSegment", + "descr": "Устанавливает сегмент ленты", + "params": [ + "номер сегмента от 0 до 7", + "глобальный номер первого пикселя в сегменте", + "глобальный номер последнего пикселя в сегменте", + "номер эффекта от 0 до 79", + "цвет в формате 0xRRGGBB, например, 0xFF0000 - красный, 0x00FF00 - зеленый, 0x0000FF - синий.", + "скорость" + ] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32*": [ + "adafruit/Adafruit NeoPixel @ ^1.12.5", + "kitesurfer1404/WS2812FX @ ^1.4.5" + ], + "esp82*": [ + "adafruit/Adafruit NeoPixel @ ^1.12.5", + "kitesurfer1404/WS2812FX @ ^1.4.5" + ] + } +} \ No newline at end of file diff --git "a/src/modules/display/LedFX/\320\237\321\200\320\270\320\274\320\265\321\200 \320\275\320\260 7 \320\264\320\270\320\276\320\264\320\276\320\262.json" "b/src/modules/display/LedFX/\320\237\321\200\320\270\320\274\320\265\321\200 \320\275\320\260 7 \320\264\320\270\320\276\320\264\320\276\320\262.json" new file mode 100644 index 00000000..c034bb0c --- /dev/null +++ "b/src/modules/display/LedFX/\320\237\321\200\320\270\320\274\320\265\321\200 \320\275\320\260 7 \320\264\320\270\320\276\320\264\320\276\320\262.json" @@ -0,0 +1,125 @@ +{ + "mark": "iotm", + "config": [ + { + "global": 0, + "type": "Reading", + "subtype": "AnalogAdc", + "id": "t1", + "widget": "anydataRed", + "page": "Сенсоры", + "descr": "Аналог", + "map": "1,1024,1,255", + "plus": 0, + "multiply": 1, + "round": 1, + "pin": 0, + "int": "1", + "avgSteps": 1 + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "b1", + "needSave": 0, + "widget": "rangeServo", + "page": "Сенсоры", + "descr": "Бар 1", + "int": "0", + "val": "0.0", + "map": "1024,1024,1,255", + "plus": 0, + "multiply": 1, + "round": 0 + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "b2", + "needSave": 0, + "widget": "rangeServo", + "page": "Сенсоры", + "descr": "Бар 2", + "int": "0", + "val": "0.0", + "map": "1024,1024,1,255", + "plus": 0, + "multiply": 1, + "round": 0 + }, + { + "global": 0, + "type": "Reading", + "subtype": "LedFX", + "id": "fl20", + "widget": "inputTxt", + "page": "Кнопки", + "descr": "Лента", + "int": 15, + "needSave": 0, + "data_pin": "2", + "numLeds": "7", + "brightness": "50", + "speed": "100", + "color": "0xFF0000", + "effectsMode": "11", + "valueMode": 0, + "show": false + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "pause", + "needSave": 0, + "widget": "toggle", + "page": "Кнопки", + "descr": "Пауза", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "bars", + "needSave": 0, + "widget": "toggle", + "page": "Кнопки", + "descr": "Бары", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "any", + "needSave": 0, + "widget": "toggle", + "page": "Кнопки", + "descr": "Анимация", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "fade", + "needSave": 0, + "widget": "toggle", + "page": "Кнопки", + "descr": "Скрыть", + "int": "0", + "val": "0" + } + ] +} + +scenario=>if bars then fl20.vuMeter(7, "t1") else fl20.stop() +if any then fl20.setEffect(100) else fl20.stop() +if pause then fl20.pause() else fl20.resume() +if fade then fl20.fadeOut(1, 3) else fl20.fadeIn(60, 3) \ No newline at end of file diff --git a/src/modules/display/Nextion/ESPNexUpload.cpp b/src/modules/display/Nextion/ESPNexUpload.cpp index 1eaf5403..2125b493 100644 --- a/src/modules/display/Nextion/ESPNexUpload.cpp +++ b/src/modules/display/Nextion/ESPNexUpload.cpp @@ -22,66 +22,24 @@ * along with this program. If not, see . * */ - -#define DEBUG_SERIAL_ENABLE -#include "ESPNexUpload.h" - -#ifdef DEBUG_SERIAL_ENABLE -#define dbSerialPrint(a) Serial.print(a) -#define dbSerialPrintHex(a) Serial.print(a, HEX) -#define dbSerialPrintln(a) Serial.println(a) -#define dbSerialBegin(a) Serial.begin(a) -#else -#define dbSerialPrint(a) \ - do \ - { \ - } while (0) -#define dbSerialPrintHex(a) \ - do \ - { \ - } while (0) -#define dbSerialPrintln(a) \ - do \ - { \ - } while (0) -#define dbSerialBegin(a) \ - do \ - { \ - } while (0) +/* +#ifdef CORE_DEBUG_LEVEL +#undef CORE_DEBUG_LEVEL #endif -ESPNexUpload::ESPNexUpload(uint32_t upload_baudrate, int line, int rx, int tx) -{ - _upload_baudrate = upload_baudrate; - _rx = rx; - _tx = tx; - _line = line; +#define CORE_DEBUG_LEVEL 3 +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +*/ -#if defined ESP8266 - nexSerial = new SoftwareSerial(_rx, _tx); -#else - if (line >= 0) { - nexSerial = new HardwareSerial(line); - // ((HardwareSerial*)nexSerial)->begin(_upload_baudrate, SERIAL_8N1, _rx, _tx); - } else { - nexSerial = new SoftwareSerial(_rx, _tx); - // ((SoftwareSerial*)nexSerial)->begin(_upload_baudrate); - } -#endif +#include "ESPNexUpload.h" -} +static const char *TAG = "nextion upload"; -void ESPNexUpload::nexSerialBegin(uint32_t _speed, int _line, int _rx, int _tx) +ESPNexUpload::ESPNexUpload(uart_port_t uart_num, uint32_t baud_rate, gpio_num_t tx_io_num, gpio_num_t rx_io_num) { -#if defined ESP8266 - nexSerial->begin(_speed); -#else - if (_line >= 0) { - ((HardwareSerial*)nexSerial)->begin(_speed, SERIAL_8N1, _rx, _tx); - } else { - ((SoftwareSerial*)nexSerial)->begin(_speed); - } -#endif + _upload_uart_lock = xSemaphoreCreateMutex(); + _uart_diver_installed = false; + setBaudrate(uart_num, baud_rate, tx_io_num, rx_io_num); } bool ESPNexUpload::connect() @@ -90,13 +48,11 @@ bool ESPNexUpload::connect() yield(); #endif - dbSerialBegin(115200); - _printInfoLine(F("serial tests & connect")); + ESP_LOGI(TAG, "serial tests & connect"); if (_getBaudrate() == 0) { - statusMessage = F("get baudrate error"); - _printInfoLine(statusMessage); + ESP_LOGE(TAG, "get baudrate error"); return false; } @@ -104,31 +60,31 @@ bool ESPNexUpload::connect() if (!_echoTest("mystop_yesABC")) { - statusMessage = F("echo test failed"); - _printInfoLine(statusMessage); + ESP_LOGE(TAG, "echo test failed"); return false; } if (!_handlingSleepAndDim()) { - statusMessage = F("handling sleep and dim settings failed"); - _printInfoLine(statusMessage); + ESP_LOGE(TAG, "handling sleep and dim settings failed"); return false; } if (!_setPrepareForFirmwareUpdate(_upload_baudrate)) { - statusMessage = F("modifybaudrate error"); - _printInfoLine(statusMessage); + ESP_LOGE(TAG, "modifybaudrate error"); return false; } return true; } -bool ESPNexUpload::prepareUpload(uint32_t file_size) +bool ESPNexUpload::prepareUpload(uint32_t file_size, bool oldProt) { + _oldProtv11 = oldProt; _undownloadByte = file_size; + ESP_LOGD(TAG, "prepareUpload: %" PRIu32, file_size); + vTaskDelay(5 / portTICK_PERIOD_MS); return this->connect(); } @@ -142,74 +98,73 @@ uint16_t ESPNexUpload::_getBaudrate(void) if (_searchBaudrate(baudrate_array[i])) { _baudrate = baudrate_array[i]; - _printInfoLine(F("baudrate determined")); + ESP_LOGI(TAG, "baudrate determined: %i", _baudrate); break; } - delay(1500); // wait for 1500 ms + vTaskDelay(1500 / portTICK_PERIOD_MS); // wait for 1500 ms } return _baudrate; } -bool ESPNexUpload::_searchBaudrate(uint32_t baudrate) +bool ESPNexUpload::_searchBaudrate(int baudrate) { #if defined ESP8266 yield(); #endif - String response = String(""); - _printInfoLine(); - dbSerialPrint(F("init nextion serial interface on baudrate: ")); - dbSerialPrintln(baudrate); + std::string response = ""; + ESP_LOGD(TAG, "init nextion serial interface on baudrate: %i", baudrate); - nexSerialBegin(baudrate, _line, _rx, _tx); - _printInfoLine(F("ESP baudrate established, try to connect to display")); - const char _nextion_FF_FF[3] = {0xFF, 0xFF, 0x00}; + setBaudrate(_upload_uart_num, + baudrate, + _upload_tx_io_num, + _upload_rx_io_num); - this->sendCommand("DRAKJHSUYDGBNCJHGJKSHBDN"); - this->sendCommand("", true, true); // 0x00 0xFF 0xFF 0xFF + ESP_LOGD(TAG, "ESP baudrate established, try to connect to display"); + const char _nextion_FF_FF[3] = {0xFF, 0xFF, 0x00}; + this->sendCommand("DRAKJHSUYDGBNCJHGJKSHBDN", true, true); // 0x00 0xFF 0xFF 0xFF this->recvRetString(response); if (response[0] != 0x1A) { - _printInfoLine(F("first indication that baudrate is wrong")); + ESP_LOGW(TAG, "first indication that baudrate is wrong"); } else { - _printInfoLine(F("first respone from display, first indication that baudrate is correct")); + ESP_LOGI(TAG, "first respone from display, first indication that baudrate is correct"); } this->sendCommand("connect"); // first connect attempt - this->recvRetString(response); - if (response.indexOf(F("comok")) == -1) + if (response.find("comok") == -1) { - _printInfoLine(F("display doesn't accept the first connect request")); + ESP_LOGW(TAG, "display doesn't accept the first connect request"); } else { - _printInfoLine(F("display accept the first connect request")); + ESP_LOGI(TAG, "display accept the first connect request"); } - response = String(""); - delay(110); // based on serial analyser from Nextion editor V0.58 to Nextion display NX4024T032_011R + vTaskDelay(110 / portTICK_PERIOD_MS); // based on serial analyser from Nextion editor V0.58 to Nextion display NX4024T032_011R this->sendCommand(_nextion_FF_FF, false); this->sendCommand("connect"); // second attempt this->recvRetString(response); - if (response.indexOf(F("comok")) == -1 && response[0] != 0x1A) + + if (response.find("comok") == -1 && response[0] != 0x1A) { - _printInfoLine(F("display doesn't accept the second connect request")); - _printInfoLine(F("conclusion, wrong baudrate")); - return 0; + ESP_LOGW(TAG, "display doesn't accept the second connect request"); + ESP_LOGW(TAG, "conclusion, wrong baudrate"); + return false; } else { - _printInfoLine(F("display accept the second connect request")); - _printInfoLine(F("conclusion, correct baudrate")); + ESP_LOGI(TAG, "display accept the second connect request"); + ESP_LOGI(TAG, "conclusion, correct baudrate"); } - return 1; + return true; } void ESPNexUpload::sendCommand(const char *cmd, bool tail, bool null_head) @@ -221,25 +176,25 @@ void ESPNexUpload::sendCommand(const char *cmd, bool tail, bool null_head) if (null_head) { - ((HardwareSerial*)nexSerial)->write(0x00); + uartWrite(0x00); } - while (nexSerial->available()) + while (uartAvailable()) { - nexSerial->read(); + uartRead(); } - nexSerial->print(cmd); + uartWriteBuf(cmd, strlen(cmd)); + if (tail) { - nexSerial->write(0xFF); - nexSerial->write(0xFF); - nexSerial->write(0xFF); + uartWrite(0xFF); + uartWrite(0xFF); + uartWrite(0xFF); } - _printSerialData(true, cmd); } -uint16_t ESPNexUpload::recvRetString(String &response, uint32_t timeout, bool recv_flag) +uint16_t ESPNexUpload::recvRetString(std::string &response, uint32_t timeout, bool recv_flag) { #if defined ESP8266 @@ -252,18 +207,19 @@ uint16_t ESPNexUpload::recvRetString(String &response, uint32_t timeout, bool re long start; bool exit_flag = false; bool ff_flag = false; - if (timeout != 500) - _printInfoLine("timeout setting serial read: " + String(timeout)); + response = ""; + // if (timeout != 500) + ESP_LOGD(TAG, "timeout setting serial read: %" PRIu32, timeout); - start = millis(); + start = (unsigned long)(esp_timer_get_time() / 1000ULL); - while (millis() - start <= timeout) + while ((unsigned long)(esp_timer_get_time() / 1000ULL) - start <= timeout) { - while (nexSerial->available()) + while (uartAvailable()) { - c = nexSerial->read(); + c = uartRead(); if (c == 0) { continue; @@ -284,7 +240,11 @@ uint16_t ESPNexUpload::recvRetString(String &response, uint32_t timeout, bool re if (recv_flag) { - if (response.indexOf(0x05) != -1) + if (response.find(0x05) != -1 && response.length() == 1) + { + exit_flag = true; + } + else if (response.find(0x08) != -1 && response.length() == 5) { exit_flag = true; } @@ -295,16 +255,12 @@ uint16_t ESPNexUpload::recvRetString(String &response, uint32_t timeout, bool re break; } } - _printSerialData(false, response); - - // if the exit flag and the ff flag are both not found, than there is a timeout - // if(!exit_flag && !ff_flag) - // _printInfoLine(F("recvRetString: timeout")); if (ff_flag) - response = response.substring(0, response.length() - 3); // Remove last 3 0xFF + response = response.substr(0, response.length() - 3); // Remove last 3 0xFF ret = response.length(); + return ret; } @@ -315,18 +271,19 @@ bool ESPNexUpload::_setPrepareForFirmwareUpdate(uint32_t upload_baudrate) yield(); #endif - String response = String(""); - String cmd = String(""); + std::string response = ""; + std::string cmd = ""; - cmd = F("00"); + cmd = "00"; this->sendCommand(cmd.c_str()); - delay(0.1); - + vTaskDelay(10 / portTICK_PERIOD_MS); this->recvRetString(response, 800, true); // normal response time is 400ms - - String filesize_str = String(_undownloadByte, 10); - String baudrate_str = String(upload_baudrate); - cmd = "whmi-wri " + filesize_str + "," + baudrate_str + ",0"; + ESP_LOGD(TAG, "response (00): %s", response.c_str()); + if (_oldProtv11) + cmd = "whmi-wri " + std::to_string(_undownloadByte) + "," + std::to_string(upload_baudrate) + ",0"; + else + cmd = "whmi-wris " + std::to_string(_undownloadByte) + "," + std::to_string(upload_baudrate) + ",1"; + ESP_LOGI(TAG, "cmd: %s", cmd.c_str()); this->sendCommand(cmd.c_str()); @@ -334,136 +291,246 @@ bool ESPNexUpload::_setPrepareForFirmwareUpdate(uint32_t upload_baudrate) // because switching to another baudrate (nexSerialBegin command) has an higher prio. // The ESP will first jump to the new 'upload_baudrate' and than process the serial 'transmit buffer' // The flush command forced the ESP to wait until the 'transmit buffer' is empty - nexSerial->flush(); - - nexSerialBegin(upload_baudrate, _line, _rx, _tx); - _printInfoLine(F("changing upload baudrate...")); - _printInfoLine(String(upload_baudrate)); + // nexSerial.flush(); + uartFlushTxOnly(); this->recvRetString(response, 800, true); // normal response time is 400ms + ESP_LOGD(TAG, "response (01): %s", response.c_str()); + // The Nextion display will, if it's ready to accept data, send a 0x05 byte. - if (response.indexOf(0x05) != -1) + if (response.find(0x05) != -1) { - _printInfoLine(F("preparation for firmware update done")); - return 1; + ESP_LOGI(TAG, "preparation for firmware update done"); + return true; } else { - _printInfoLine(F("preparation for firmware update failed")); - return 0; + ESP_LOGE(TAG, "preparation for firmware update failed"); + return false; } } -void ESPNexUpload::setUpdateProgressCallback(THandlerFunction value) -{ - _updateProgressCallback = value; -} - -bool ESPNexUpload::upload(const uint8_t *file_buf, size_t buf_size) +// НЕ ПРОВЕРЯЛОСЬ !!!!!!!!!!!!!!!!!! +bool ESPNexUpload::upload(const uint8_t *file_buf, size_t file_size) { #if defined ESP8266 yield(); #endif - uint8_t c; + // uint8_t c; uint8_t timeout = 0; - String string = String(""); - - for (uint16_t i = 0; i < buf_size; i++) + std::string response = ""; + uint8_t sent_bulk_counter = 0; + // uint8_t buff[4096] = {0}; + uint32_t offset = 0; + int remainingBlocks = ceil(file_size / 4096); + int blockSize = 4096; + + while (remainingBlocks > 0) { - - // Users must split the .tft file contents into 4096 byte sized packets with the final partial packet size equal to the last remaining bytes (<4096 bytes). - if (_sent_packets == 4096) + if (remainingBlocks == 1) { - - // wait for the Nextion to return its 0x05 byte confirming reception and readiness to receive the next packets - this->recvRetString(string, 500, true); - if (string.indexOf(0x05) != -1) + blockSize = file_size - offset; + } + uartWriteBuf((char *)file_buf[offset], blockSize); + // wait for the Nextion to return its 0x05 byte confirming reception and readiness to receive the next packets + this->recvRetString(response, 2000, true); + + ESP_LOGE(TAG, "response [%s]", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + + if (response[0] == 0x08 && response.size() == 5) + { // handle partial upload request + remainingBlocks -= 1; + _sent_packets_total += blockSize; + sent_bulk_counter++; + if (sent_bulk_counter % 10 == 0) { + ESP_LOGI(TAG, "bulk: %i, total bytes %" PRIu32 ", response: %s", sent_bulk_counter, _sent_packets_total, response.c_str()); + } - // reset sent packets counter - _sent_packets = 0; + // ESP_LOGE(TAG, "response [%s]", + // format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); - // reset receive String - string = ""; + for (int j = 0; j < 4; ++j) + { + offset += static_cast(response[j + 1]) << (8 * j); + ESP_LOGI(TAG, "Offset : %" PRIu32, offset); } - else + if (offset) + remainingBlocks = ceil((file_size - offset) / blockSize); + } + else if (response[0] == 0x05) + { + remainingBlocks -= 1; + _sent_packets_total += blockSize; + sent_bulk_counter++; + if (sent_bulk_counter % 10 == 0) { - if (timeout >= 8) - { - statusMessage = F("serial connection lost"); - _printInfoLine(statusMessage); - return false; - } - - timeout++; + ESP_LOGI(TAG, "bulk: %i, total bytes %" PRIu32 ", response: %s", sent_bulk_counter, _sent_packets_total, response.c_str()); } - // delay current byte - i--; + // ESP_LOGE(TAG, "response [%s]", + // format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + offset += 4096; } + else { - - // read buffer - c = file_buf[i]; - - // write byte to nextion over serial - nexSerial->write(c); - - // update sent packets counter - _sent_packets++; + if (timeout >= 2) + { + ESP_LOGE(TAG, "upload failed, no valid response from display, total bytes send : %" PRIu32, _sent_packets_total); + sent_bulk_counter = 0; + return false; + } + timeout++; } } - + ESP_LOGI(TAG, "upload send last bytes %" PRIu32 ", response: %s", _sent_packets_total, response.c_str()); + // ESP_LOGI(TAG,"upload finished, total bytes send : %"PRIu32, _sent_packets_total); + sent_bulk_counter = 0; return true; } bool ESPNexUpload::upload(Stream &myFile) { + #if defined ESP8266 yield(); #endif - - // create buffer for read - uint8_t buff[4096] = {0}; - - // read all data from server - while (_undownloadByte > 0 || _undownloadByte == -1) + // uint8_t c; + uint8_t timeout = 0; + std::string response = ""; + uint8_t sent_bulk_counter = 0; + uint8_t file_buf[4096] = {0}; + uint32_t offset = 0; + uint32_t _seekByte = 0; + uint32_t _packets_total_byte = 0; + // get available data size + size_t file_size = _undownloadByte; // myFile.available(); + if (file_size) { - - // get available data size - size_t size = myFile.available(); - - if (size) + int remainingBlocks = ceil(file_size / 4096.); + int blockSize = 4096; + ESP_LOGI(TAG, "Remaining Blocks ALL: %" PRIu32, remainingBlocks); + while (remainingBlocks > 0) { - // read up to 2048 byte into the buffer - int c = myFile.readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); - - // Write the buffered bytes to the nextion. If this fails, return false. - if (!this->upload(buff, c)) + // myFile.available(); + // read up to 4096 byte into the buffer + if (_seekByte > 0) { - return false; + if (file_size > _seekByte) + { + blockSize = myFile.readBytes(file_buf, _seekByte); + // file_size = myFile.available(); + file_size -= _seekByte; + ESP_LOGI(TAG, "Seek file: %" PRIu32 ", left bytes %" PRIu32, _seekByte, file_size); + } + else + { + ESP_LOGE(TAG, "File failed seek"); + return false; + } + blockSize = myFile.readBytes(file_buf, ((file_size > sizeof(file_buf)) ? sizeof(file_buf) : file_size)); + file_size -= blockSize; // осталось байт + } + else + { + blockSize = myFile.readBytes(file_buf, ((file_size > sizeof(file_buf)) ? sizeof(file_buf) : file_size)); + file_size -= blockSize; // осталось байт } + uartWriteBuf((char *)file_buf, blockSize); + // wait for the Nextion to return its 0x05 byte confirming reception and readiness to receive the next packets + if (response[0] == 0x08 || timeout >= 4) + this->recvRetString(response, 2000, true); else + this->recvRetString(response, 500, true); + + ESP_LOGE(TAG, "upload response byte [%s]", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + ESP_LOG_BUFFER_HEX(TAG, response.data(), response.size()); + if (response[0] == 0x08 && response.size() == 5) + { // handle partial upload request + remainingBlocks -= 1; + _sent_packets_total += blockSize; // отправлено байт + _packets_total_byte += blockSize; // всего байт отправлено или пропущено + sent_bulk_counter++; + if (sent_bulk_counter % 10 == 0) + { + // ESP_LOGI(TAG, "bulk: %i, total bytes %" PRIu32 ", response: %s", sent_bulk_counter, _sent_packets_total, response.c_str()); + } + ESP_LOGE(TAG, "upload response [%s]", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + for (int j = 0; j < 4; ++j) + { + offset += static_cast(response[j + 1]) << (8 * j); + } + ESP_LOGI(TAG, "Offset : %" PRIu32, offset); + if (offset) + { + remainingBlocks = ceil((file_size - offset) / blockSize); + _seekByte = offset - _packets_total_byte; + _packets_total_byte += _seekByte; + ESP_LOGI(TAG, "Seek Byte : %" PRIu32, _seekByte); + ESP_LOGI(TAG, "Remaining Blocks : %" PRIu32, remainingBlocks); + } + } + else if ((response[0] == 0x08 || response[0] == 0x05) && response.size() == 1) { - if (_updateProgressCallback) + remainingBlocks -= 1; + _sent_packets_total += blockSize; + _packets_total_byte += blockSize; + file_size -= blockSize; + sent_bulk_counter++; + if (sent_bulk_counter % 10 == 0) { - _updateProgressCallback(); + // ESP_LOGI(TAG, "bulk: %i, total bytes %" PRIu32 ", response: %s", sent_bulk_counter, _sent_packets_total, response.c_str()); } + + ESP_LOGE(TAG, "upload response [%s]", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + offset += 4096; } - if (_undownloadByte > 0) + else { - _undownloadByte -= c; + ESP_LOGE(TAG, "Fail response [%s]", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + if (timeout >= 9) + { + ESP_LOGE(TAG, "upload failed, no valid response from display, total bytes send : %" PRIu32, _sent_packets_total); + sent_bulk_counter = 0; + return false; + } + timeout++; } + ESP_LOGI(TAG, "bulk: %i, total bytes %" PRIu32 ", response: %s", sent_bulk_counter, _sent_packets_total, response.c_str()); + } + this->recvRetString(response, 3000, true); + if (response[0] == 0x88) + { + ESP_LOGI(TAG, "upload finished (Response 0x88), total bytes send : %" PRIu32, _sent_packets_total); + this->end(); + } + else + { + ESP_LOGE(TAG, "upload response [%s]", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str()); + ESP_LOGI(TAG, "upload finished (TimeOut 0x88), total bytes send : %" PRIu32, _sent_packets_total); + this->end(); } - delay(1); + // ESP_LOGI(TAG, "upload send last bytes %" PRIu32 ", response: %s", _sent_packets_total, response.c_str()); + // ESP_LOGI(TAG,"upload finished, total bytes send : %"PRIu32, _sent_packets_total); + sent_bulk_counter = 0; + return true; + } + else + { + ESP_LOGE(TAG, "File failed available"); + return false; } - - return true; } void ESPNexUpload::softReset(void) @@ -475,139 +542,129 @@ void ESPNexUpload::softReset(void) void ESPNexUpload::end() { + if (_upload_uart_num == NULL) + { + return; + } + // wait for the nextion to finish internal processes - delay(1600); + // vTaskDelay(1600 / portTICK_PERIOD_MS); // soft reset the nextion this->softReset(); // end Serial connection - ((HardwareSerial*)nexSerial)->end(); + // uart_mutex_lock(); + // ESP_ERROR_CHECK(uart_driver_delete(_upload_uart_num)); + // uart_mutex_unlock(); // reset sent packets counter - _sent_packets = 0; + //_sent_packets = 0; + _sent_packets_total = 0; - statusMessage = F("upload ok"); - _printInfoLine(statusMessage + F("\r\n")); + ESP_LOGI(TAG, "serial connection closed"); } void ESPNexUpload::_setRunningMode(void) { - String cmd = String(""); - delay(100); - cmd = F("runmod=2"); - this->sendCommand(cmd.c_str()); - delay(60); + vTaskDelay(100 / portTICK_PERIOD_MS); + this->sendCommand("runmod=2"); + vTaskDelay(60 / portTICK_PERIOD_MS); } -bool ESPNexUpload::_echoTest(String input) +bool ESPNexUpload::_echoTest(std::string input) { - String cmd = String(""); - String response = String(""); - cmd = "print \"" + input + "\""; - this->sendCommand(cmd.c_str()); + std::string response = ""; + std::string cmd = "print \"" + input + "\""; + this->sendCommand(cmd.c_str()); uint32_t duration_ms = calculateTransmissionTimeMs(cmd) * 2 + 10; // times 2 (send + receive) and 10 ms extra this->recvRetString(response, duration_ms); - return (response.indexOf(input) != -1); + return (response.find(input) != -1); } bool ESPNexUpload::_handlingSleepAndDim(void) { - String cmd = String(""); - String response = String(""); + + std::string response = ""; bool set_sleep = false; bool set_dim = false; - cmd = F("get sleep"); - this->sendCommand(cmd.c_str()); - + this->sendCommand("get sleep"); this->recvRetString(response); if (response[0] != 0x71) { - statusMessage = F("unknown response from 'get sleep' request"); - _printInfoLine(statusMessage); + ESP_LOGE(TAG, "unknown response from 'get sleep' request"); return false; } if (response[1] != 0x00) { - _printInfoLine(F("sleep enabled")); + ESP_LOGD(TAG, "sleep enabled"); set_sleep = true; } else { - _printInfoLine(F("sleep disabled")); + ESP_LOGD(TAG, "sleep disabled"); } - response = String(""); - cmd = F("get dim"); - this->sendCommand(cmd.c_str()); - + this->sendCommand("get dim"); this->recvRetString(response); if (response[0] != 0x71) { - statusMessage = F("unknown response from 'get dim' request"); - _printInfoLine(statusMessage); + ESP_LOGE(TAG, "unknown response from 'get dim' request"); return false; } if (response[1] == 0x00) { - _printInfoLine(F("dim is 0%, backlight from display is turned off")); + ESP_LOGD(TAG, "dim is 0%%, backlight from display is turned off"); set_dim = true; } else { - _printInfoLine(); - dbSerialPrint(F("dim ")); - dbSerialPrint((uint8_t)response[1]); - dbSerialPrintln(F("%")); + ESP_LOGD(TAG, "dim %i%%", (uint8_t)response[1]); } if (!_echoTest("ABC")) { - statusMessage = F("echo test in 'handling sleep and dim' failed"); - _printInfoLine(statusMessage); + ESP_LOGE(TAG, "echo test in 'handling sleep and dim' failed"); return false; } if (set_sleep) { - cmd = F("sleep=0"); - this->sendCommand(cmd.c_str()); + this->sendCommand("sleep=0"); // Unfortunately the display doesn't send any respone on the wake up request (sleep=0) // Let the ESP wait for one second, this is based on serial analyser from Nextion editor V0.58 to Nextion display NX4024T032_011R // This gives the Nextion display some time to wake up - delay(1000); + vTaskDelay(1000 / portTICK_PERIOD_MS); } if (set_dim) { - cmd = F("dim=100"); - this->sendCommand(cmd.c_str()); - delay(15); + this->sendCommand("dim=100"); + vTaskDelay(15 / portTICK_PERIOD_MS); } return true; } -void ESPNexUpload::_printSerialData(bool esp_request, String input) +void ESPNexUpload::_printSerialData(bool esp_request, std::string input) { - char c; if (esp_request) - dbSerialPrint(F("ESP request: ")); + ESP_LOGI(TAG, "ESP request: "); else - dbSerialPrint(F("Nextion respone: ")); + ESP_LOGI(TAG, "Nextion respone: "); if (input.length() == 0) { - dbSerialPrintln(F("none")); + ESP_LOGW(TAG, "none"); return; } @@ -616,18 +673,15 @@ void ESPNexUpload::_printSerialData(bool esp_request, String input) c = input[i]; if ((uint8_t)c >= 0x20 && (uint8_t)c <= 0x7E) - dbSerialPrint(c); + printf("%c", c); else { - dbSerialPrint(F("0x")); - dbSerialPrintHex(c); - dbSerialPrint(F(" ")); + printf("0x\\%02hhx", c); } } - dbSerialPrintln(); } -uint32_t ESPNexUpload::calculateTransmissionTimeMs(String message) +uint32_t ESPNexUpload::calculateTransmissionTimeMs(std::string message) { // In general, 1 second (s) = 1000 (10^-3) millisecond (ms) or // 1 second (s) = 1000 000 (10^-6) microsecond (us). @@ -645,13 +699,165 @@ uint32_t ESPNexUpload::calculateTransmissionTimeMs(String message) uint32_t duration_message_us = nr_of_bytes * duration_one_byte_us; uint32_t return_value_ms = duration_message_us / 1000; - _printInfoLine("calculated transmission time: " + String(return_value_ms) + " ms"); + ESP_LOGD(TAG, "calculated transmission time: %" PRIu32 " ms", return_value_ms); return return_value_ms; } -void ESPNexUpload::_printInfoLine(String line) +uint32_t ESPNexUpload::uartAvailable() +{ + if (_upload_uart_num == NULL) + { + return 0; + } + + uart_mutex_lock(); + size_t available; + uart_get_buffered_data_len(_upload_uart_num, &available); + if (_upload_uart_has_peek) + available++; + uart_mutex_unlock(); + return available; +} + +uint8_t ESPNexUpload::uartRead() +{ + if (_upload_uart_num == NULL) + { + return 0; + } + uint8_t c = 0; + + uart_mutex_lock(); + if (_upload_uart_has_peek) + { + _upload_uart_has_peek = false; + c = _upload_uart_peek_byte; + } + else + { + + int len = uart_read_bytes(_upload_uart_num, &c, 1, 20 / portTICK_RATE_MS); + if (len == 0) + { + c = 0; + } + } + uart_mutex_unlock(); + return c; +} + +void ESPNexUpload::uartWrite(uint8_t c) { - dbSerialPrint(F("Status info: ")); - if (line.length() != 0) - dbSerialPrintln(line); + if (_upload_uart_num == NULL) + { + return; + } + uart_mutex_lock(); + uart_write_bytes(_upload_uart_num, &c, 1); + uart_mutex_unlock(); +} + +void ESPNexUpload::uartWriteBuf(const char *data, size_t len) +{ + if (_upload_uart_num == NULL || data == NULL || !len) + { + return; + } + + uart_mutex_lock(); + uart_write_bytes(_upload_uart_num, data, len); + uart_mutex_unlock(); +} + +void ESPNexUpload::uartFlushTxOnly() +{ + + bool txOnly = true; + if (_upload_uart_num == NULL) + { + return; + } + + uart_mutex_lock(); + ESP_ERROR_CHECK(uart_wait_tx_done(_upload_uart_num, portMAX_DELAY)); + + if (!txOnly) + { + ESP_ERROR_CHECK(uart_flush_input(_upload_uart_num)); + } + uart_mutex_unlock(); +} + +void ESPNexUpload::setBaudrate(uart_port_t uart_num, uint32_t baud_rate, gpio_num_t tx_io_num, gpio_num_t rx_io_num) +{ + + ESP_LOGD(TAG, "installing driver on uart %d with baud rate %d", uart_num, baud_rate); + + _upload_uart_num = uart_num; + _upload_baudrate = baud_rate; + _upload_tx_io_num = tx_io_num; + _upload_rx_io_num = rx_io_num; + + const uart_config_t uart_config = { + .baud_rate = (int)baud_rate, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE}; + + // Do not change the UART initialization order. + // This order was gotten from: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html + + ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config)); + if (_uart_diver_installed == true) + { + ESP_LOGD(TAG, "baud rate changed"); + return; + } + + ESP_ERROR_CHECK(uart_set_pin(uart_num, tx_io_num, rx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + /* + ESP_ERROR_CHECK(uart_driver_install(uart_num, + CONFIG_NEX_UART_RECV_BUFFER_SIZE, // Receive buffer size. + 0, // Transmit buffer size. + 10, // Queue size. + NULL, // Queue pointer. + 0)); // Allocation flags. + */ + ESP_LOGD(TAG, "driver installed"); + _uart_diver_installed = true; +} + +std::string ESPNexUpload::str_snprintf(const char *fmt, size_t len, ...) +{ + std::string str; + va_list args; + + str.resize(len); + va_start(args, len); + size_t out_length = vsnprintf(&str[0], len + 1, fmt, args); + va_end(args); + + if (out_length < len) + str.resize(out_length); + + return str; +} +char ESPNexUpload::format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +std::string ESPNexUpload::format_hex_pretty(const uint8_t *data, size_t length) +{ + if (length == 0) + return ""; + std::string ret; + ret.resize(3 * length - 1); + for (size_t i = 0; i < length; i++) + { + ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (i != length - 1) + ret[3 * i + 2] = '.'; + } + if (length > 4) + return ret + " (" + str_snprintf("%u", 32, length) + ")"; + return ret; } \ No newline at end of file diff --git a/src/modules/display/Nextion/ESPNexUpload.h b/src/modules/display/Nextion/ESPNexUpload.h index 3a22ac15..bc57852f 100644 --- a/src/modules/display/Nextion/ESPNexUpload.h +++ b/src/modules/display/Nextion/ESPNexUpload.h @@ -1,8 +1,17 @@ /** * @file NexUpload.h - * The definition of class NexUpload. - * - * + * The definition of class NexUpload. + * + * 1 - Removed all the Arduino code and replaced it by ESP-IDF + * 2 - Removed hard-coded UART configuration, see ESPNexUpload constructor + * 3 - Removed statusMessage and the function _printInfoLine + * 4 - Removed call-back functionality + * 5 - Removed one out of two upload functions + * 6 - BugFix in upload function + * @author Machiel Mastenbroek (machiel.mastenbroek@gmail.com) + * @date 2022/08/14 + * @version 0.6.0 + * * 1 - BugFix when display baudrate is diffrent from initial ESP baudrate * 2 - Improved debug information * 3 - Make delay commands dependent on the baudrate @@ -10,12 +19,12 @@ * @date 2019/11/04 * @version 0.5.5 * - * Stability improvement, Nextion display doesn’t freeze after the seconds 4096 trance of firmware bytes. - * Now the firmware upload process is stabled without the need of a hard Display power off-on intervention. - * Undocumented features (not mentioned in nextion-hmi-upload-protocol-v1-1 specification) are added. - * This implementation is based in on a reverse engineering with a UART logic analyser between + * Stability improvement, Nextion display doesn’t freeze after the seconds 4096 trance of firmware bytes. + * Now the firmware upload process is stabled without the need of a hard Display power off-on intervention. + * Undocumented features (not mentioned in nextion-hmi-upload-protocol-v1-1 specification) are added. + * This implementation is based in on a reverse engineering with a UART logic analyser between * the Nextion editor v0.58 and a NX4024T032_011R Display. - * + * * @author Machiel Mastenbroek (machiel.mastenbroek@gmail.com) * @date 2019/10/24 * @version 0.5.0 @@ -24,7 +33,7 @@ * @author Onno Dirkzwager (onno.dirkzwager@gmail.com) * @date 2018/12/26 * @version 0.3.0 - * + * * Modified to work with ESP8266 and SoftwareSerial * @author Ville Vilpas (psoden@gmail.com) * @date 2018/2/3 @@ -33,44 +42,46 @@ * Original version (a part of https://github.com/itead/ITEADLIB_Arduino_Nextion) * @author Chen Zengpeng (email:) * @date 2016/3/29 - * @copyright + * @copyright * Copyright (C) 2014-2015 ITEAD Intelligent Systems Co., Ltd. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * - * You should have received a copy of the GNU General Public License + * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef __ESPNEXUPLOAD_H__ #define __ESPNEXUPLOAD_H__ -#include +//#include +#include /* printf, scanf, NULL */ + +//#include +#include "esp_log.h" +//#include "freertos/FreeRTOS.h" +//#include "freertos/task.h" +//#include "driver/gpio.h" +#include "driver/uart.h" + +//#include "hal/uart_types.h" #include #include -#ifdef ESP8266 -#include -#else -#include -#include -#endif +#define CONFIG_NEX_UART_RECV_BUFFER_SIZE 256 /** - * @addtogroup CoreAPI - * @{ + * @addtogroup CoreAPI + * @{ */ -// callback template definition -typedef std::function THandlerFunction; - /** * * Provides the API for nextion to upload the ftf file. @@ -78,81 +89,68 @@ typedef std::function THandlerFunction; class ESPNexUpload { public: /* methods */ - // callback template definition - typedef std::function THandlerFunction; - + /** - * Constructor. - * - * @param uint32_t upload_baudrate - set upload baudrate. + * Constructor. + * */ - ESPNexUpload(uint32_t upload_baudrate, int line, int rx, int tx); - + ESPNexUpload(uart_port_t uart_num, uint32_t baud_rate, gpio_num_t tx_io_num, gpio_num_t rx_io_num); + /** - * destructor. - * + * destructor. + * */ - ~ESPNexUpload() {} - + ~ESPNexUpload(){} + /** * Connect to Nextion over serial * * @return true or false. */ bool connect(); - + /** * prepare upload. Set file size & Connect to Nextion over serial * * @return true if success, false for failure. */ - bool prepareUpload(uint32_t file_size); - - /** - * set Update Progress Callback. (What to do during update progress) - * - * @return none - */ - void setUpdateProgressCallback(THandlerFunction value); - + bool prepareUpload(uint32_t file_size, bool oldProt); + /** - * start update tft file to nextion. - * + * start update tft file to nextion. + * * @param const uint8_t *file_buf * @param size_t buf_size * @return true if success, false for failure. */ bool upload(const uint8_t *file_buf, size_t buf_size); - /** - * start update tft file to nextion. - * - * @param Stream &myFile - * @return true if success, false for failure. - */ bool upload(Stream &myFile); - /** * Send reset command to Nextion over serial * * @return none. */ - void softReset(void); + void softReset(void); /** * Send reset, end serial, reset _sent_packets & update status message * * @return none. */ - void end(void); - -public: /* data */ - String statusMessage = ""; - + void end(void); + private: /* methods */ /* - * get communicate baudrate. + * Semaphore construction to prevent double UART actions * + */ + void uart_mutex_lock(void) {do {} while (xSemaphoreTake(_upload_uart_lock, portMAX_DELAY) != pdPASS);}; + void uart_mutex_unlock(void) {xSemaphoreGive(_upload_uart_lock);}; + + /* + * get communicate baudrate. + * * @return communicate baudrate. * */ @@ -162,127 +160,167 @@ class ESPNexUpload * search communicate baudrate. * * @param baudrate - communicate baudrate. - * - * @return true if success, false for failure. + * + * @return true if success, false for failure. */ - bool _searchBaudrate(uint32_t baudrate); + bool _searchBaudrate(int baudrate); /* * set download baudrate. * * @param baudrate - set download baudrate. - * - * @return true if success, false for failure. + * + * @return true if success, false for failure. */ bool _setPrepareForFirmwareUpdate(uint32_t upload_baudrate); /* * set Nextion running mode. * - * Undocumented feature of the Nextion protocol. - * It's used by the 'upload to Nextion device' feature of the Nextion Editor V0.58 - * - * The nextion display doesn't send any response - * + * Undocumented feature of the Nextion protocol. + * It's used by the 'upload to Nextion device' feature of the Nextion Editor V0.58 + * + * The nextion display doesn't send any response + * */ void _setRunningMode(void); /* - * Test UART nextion connection availability - * - * @param input - echo string, - * - * @return true when the 'echo string' that is send is equal to the received string - * - * This test is used by the 'upload to Nextion device' feature of the Nextion Editor V0.58 - * + * Test UART nextion connection availability + * + * @param input - echo string, + * + * @return true when the 'echo string' that is send is equal to the received string + * + * This test is used by the 'upload to Nextion device' feature of the Nextion Editor V0.58 + * */ - bool _echoTest(String input); - + bool _echoTest(std::string input); + /* * This function get the sleep and dim value from the Nextion display. * - * If sleep = 1 meaning: sleep is enabled + * If sleep = 1 meaning: sleep is enabled * action : sleep will be disabled - * If dim = 0, meaning: the display backlight is turned off - * action : dim will be set to 100 (percent) - * + * If dim = 0, meaning: the display backlight is turned off + * action : dim will be set to 100 (percent) + * */ bool _handlingSleepAndDim(void); - + /* * This function (debug) print the Nextion response to a human readable string * - * @param esp_request - true: request message from esp to nextion - * false: response message from nextion to esp - * - * @param input - string to print - * + * @param esp_request - true: request message from esp to nextion + * false: response message from nextion to esp + * + * @param input - string to print + * */ - void _printSerialData(bool esp_request, String input); - - /* - * This function print a prefix debug line - * - * @param line: optional debug/ info line - */ - void _printInfoLine(String line = ""); - + void _printSerialData(bool esp_request, std::string input); + /* * Send command to Nextion. * * @param cmd - the string of command. - * @param tail - end the string with tripple 0xFF byte - * @param null_head - start the string with a single 0x00 byte + * @param tail - end the string with tripple 0xFF byte + * @param null_head - start the string with a single 0x00 byte * * @return none. */ - void sendCommand(const char *cmd, bool tail = true, bool null_head = false); + void sendCommand(const char* cmd, bool tail = true, bool null_head = false); /* - * Receive string data. - * - * @param buffer - save string data. - * @param timeout - set timeout time. + * Receive string data. + * + * @param buffer - save string data. + * @param timeout - set timeout time. * @param recv_flag - if recv_flag is true,will braak when receive 0x05. * * @return the length of string buffer. * - */ - uint16_t recvRetString(String &string, uint32_t timeout = 500, bool recv_flag = false); + */ + uint16_t recvRetString(std::string &string, uint32_t timeout = 500,bool recv_flag = false); /* - * - * This function calculates the transmission time, the transmission time + * + * This function calculates the transmission time, the transmission time * is based on the length of the message and the baudrate. - * - * @param message - only used to determine the length of the message + * + * @param message - only used to determine the length of the message * * @return time in us length of string buffer. * */ - uint32_t calculateTransmissionTimeMs(String message); + uint32_t calculateTransmissionTimeMs(std::string message); + + /* + * Setup UART for communication with display + * + * @param uart_num - UART number + * @param baud_rate - baud rate speed + * @param tx_io_num - GPIO TX pin + * @param rx_io_num - GPIO RX pin + * + */ + void setBaudrate(uart_port_t uart_num, uint32_t baud_rate, gpio_num_t tx_io_num, gpio_num_t rx_io_num); + + /* + * Check is UART is avaialble + */ + uint32_t uartAvailable(); + + /* + * Read one RX byte + * + * @return one received UART byte + */ + uint8_t uartRead(); + + /* + * Write one TX byte + * + * @param c - one byte + * + */ + void uartWrite(uint8_t c); - void nexSerialBegin(uint32_t upload_baudrate, int line, int rx, int tx); + /* + * Write char string + * + * @param data - char string of data to send + * @param len - length of the string + * + */ + void uartWriteBuf(const char * data, size_t len); + + /* + * Clear TX UART buffer + */ + void uartFlushTxOnly(); -private: /* data */ - uint32_t _baudrate; /* nextion serail baudrate */ - uint32_t _undownloadByte; /* undownload byte of tft file */ - uint32_t _upload_baudrate; /* upload baudrate */ - uint16_t _sent_packets = 0; /* upload baudrate */ - uint8_t _rx; - uint8_t _tx; - uint8_t _line; - THandlerFunction _updateProgressCallback; +private: /* data */ + bool _oldProtv11; + uint32_t _baudrate; /* nextion serail baudrate */ + uint32_t _undownloadByte; /* undownload byte of tft file */ + uart_port_t _upload_uart_num; /* upload uart port number */ + uint32_t _upload_baudrate; /* upload baudrate */ + gpio_num_t _upload_tx_io_num; /* upload gpio TX */ + gpio_num_t _upload_rx_io_num; /* upload gpio RX */ + xSemaphoreHandle _upload_uart_lock; /* semaphore to prevent double UART actions */ + bool _upload_uart_has_peek; /* UART RX peek flag */ + uint8_t _upload_uart_peek_byte; /* UART RX peek byte */ + //uint16_t _sent_packets = 0; /* _sent_packets till 4096 bytes */ + uint32_t _sent_packets_total = 0; /* total number of uploaded display firmware bytes */ + bool _uart_diver_installed; /* flag, if true UART is installed */ -#ifdef ESP8266 - SoftwareSerial* nexSerial; -#else - Stream* nexSerial; -#endif +std::string str_snprintf(const char *fmt, size_t len, ...); + /// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. +std::string format_hex_pretty(const uint8_t *data, size_t length); +static char format_hex_pretty_char(uint8_t v); }; /** * @} */ -#endif /* #ifndef __ESPNEXUPLOAD_H__ */ \ No newline at end of file +#endif /* #ifndef __ESPNEXUPLOAD_H__ */ diff --git a/src/modules/display/Nextion/Nextion.cpp b/src/modules/display/Nextion/Nextion.cpp index a1a25c33..73fc69a3 100644 --- a/src/modules/display/Nextion/Nextion.cpp +++ b/src/modules/display/Nextion/Nextion.cpp @@ -13,7 +13,8 @@ class Nextion : public IoTUart int _tx, _rx, _speed, _line; bool _UpTelegram; char _inc; - String _inStr = ""; // буфер приема строк в режимах 0, 1, 2 + String _inStr = ""; // буфер приема строк в режимах 0, 1, 2 + bool _oldProt; // Выводим русские буквы на экран Nextion (преобразуем в кодировку ISO-8859-5) String convertRUS(String text) @@ -52,7 +53,7 @@ class Nextion : public IoTUart } } return out; - } + } public: Nextion(String parameters) : IoTUart(parameters) @@ -62,9 +63,11 @@ class Nextion : public IoTUart _host = jsonReadStr(parameters, "host"); jsonRead(parameters, "rx", _rx); jsonRead(parameters, "tx", _tx); - jsonRead(parameters, "speed", _speed); - jsonRead(parameters, "line", _line); + jsonRead(parameters, "speed", _speed); + jsonRead(parameters, "line", _line); jsonRead(parameters, "uploadTelegram", _UpTelegram); + if (!jsonRead(parameters, "oldProt_v11", _oldProt)) + _oldProt = false; } IoTValue execute(String command, std::vector ¶m) @@ -74,15 +77,15 @@ class Nextion : public IoTUart { updateServer(); } - else if (command == "printFFF") + else if (command == "printFFF") { - if (param.size() == 2) - //UART.printFFF("auto.val=1",0) + if (param.size() == 2) + // UART.printFFF("auto.val=1",0) { String strToUart = ""; strToUart = param[0].valS; - if (param[1].valD) + if (param[1].valD) uartPrintFFF("\"" + strToUart + "\""); else uartPrintFFF(strToUart); @@ -99,7 +102,7 @@ class Nextion : public IoTUart else uartPrintFFF(strToUart + param[1].valS); } - } + } // отправка кирилических символов на Nextion (русские буквы) else if (command == "printRusFFF") { @@ -123,43 +126,54 @@ class Nextion : public IoTUart else uartPrintFFF(convertRUS(strToUart + param[1].valS)); } - }// else { // не забываем, что переопределяем execute и нужно проверить что в базовом классе проверяется - // return IoTUart::execute(command, param); - // } - return {}; + } // else { // не забываем, что переопределяем execute и нужно проверить что в базовом классе проверяется + // return IoTUart::execute(command, param); + // } + return {}; } - void onModuleOrder(String &key, String &value) { - if (key == "uploadServer") { + void onModuleOrder(String &key, String &value) + { + if (key == "uploadServer") + { updateServer(); } } - void uartPrintFFF(const String& msg) { - if (_myUART) { - SerialPrint("I", F("Nextion"), "uartPrintFFF -> "+msg+" +FFFFFF"); + void uartPrintFFF(const String &msg) + { + if (_myUART) + { + SerialPrint("I", F("Nextion"), "uartPrintFFF -> " + msg + " +FFFFFF"); _myUART->print(msg); _myUART->write(0xff); _myUART->write(0xff); _myUART->write(0xff); } } -//---------------------NEXTION-UART---START------------------------ - void uartHandle() { - if (!_myUART) return; - if (_myUART->available()) { + //---------------------NEXTION-UART---START------------------------ + void uartHandle() + { + if (!_myUART) + return; + if (_myUART->available()) + { _inc = _myUART->read(); - if (_inc == 0xFF) { + if (_inc == 0xFF) + { _inc = _myUART->read(); _inc = _myUART->read(); _inStr = ""; return; } - if (_inc == '\r') return; - - if (_inc == '\n') { - if (_inStr.indexOf("=") == -1) { // если входящее сообщение не по формату, то работаем как в режиме 0 + if (_inc == '\r') + return; + + if (_inc == '\n') + { + if (_inStr.indexOf("=") == -1) + { // если входящее сообщение не по формату, то работаем как в режиме 0 setValue(_inStr); return; } @@ -170,68 +184,79 @@ class Nextion : public IoTUart id.replace(".txt", "_txt"); generateOrder(id, valStr); _inStr = ""; - } else _inStr += _inc; + } + else + _inStr += _inc; } } - void onRegEvent(IoTItem* eventItem) { - if (!_myUART || !eventItem) return; + void onRegEvent(IoTItem *eventItem) + { + if (!_myUART || !eventItem) + return; int indexOf_; String printStr = ""; - printStr += eventItem->getID(); - indexOf_ = printStr.indexOf("_"); - if (indexOf_ == -1) return; // пропускаем событие, если нет используемого признака типа данных - _txt или _vol - - if (printStr.indexOf("_txt") > 0) { - printStr.replace("_txt", ".txt=\""); - printStr += eventItem->getValue(); - printStr += "\""; - } else if (printStr.indexOf("_val") > 0) { - printStr += eventItem->getValue(); - printStr.replace(".", ""); - printStr.replace("_val", ".val="); - } else { - if (indexOf_ == printStr.length()-1) printStr.replace("_", ""); - else printStr.replace("_", "."); - printStr += "="; - printStr += eventItem->getValue(); - } + printStr += eventItem->getID(); + indexOf_ = printStr.indexOf("_"); + if (indexOf_ == -1) + return; // пропускаем событие, если нет используемого признака типа данных - _txt или _vol - uartPrintFFF(convertRUS(printStr)); + if (printStr.indexOf("_txt") > 0) + { + printStr.replace("_txt", ".txt=\""); + printStr += eventItem->getValue(); + printStr += "\""; + } + else if (printStr.indexOf("_val") > 0) + { + printStr += eventItem->getValue(); + printStr.replace(".", ""); + printStr.replace("_val", ".val="); + } + else + { + if (indexOf_ == printStr.length() - 1) + printStr.replace("_", ""); + else + printStr.replace("_", "."); + printStr += "="; + printStr += eventItem->getValue(); + } + + uartPrintFFF(convertRUS(printStr)); } - -//---------------------NEXTION-UART---END------------------------ -//---------------------NEXTION-UPDATE---START------------------------ + //---------------------NEXTION-UART---END------------------------ + + //---------------------NEXTION-UPDATE---START------------------------ void updateServer() { - SerialPrint("I", F("NextionUpdate"), "Update .... "); + SerialPrint("I", F("NextionUpdate"), "Update .... "); - if (!updated) - { - SerialPrint("I", F("NextionUpdate"), "connecting to " + (String)_host); - HTTPClient http; + if (!updated) + { + SerialPrint("I", F("NextionUpdate"), "connecting to " + (String)_host); + HTTPClient http; #if defined ESP8266 - WiFiClient client; - if (!http.begin(client, _host, 80, _url)) - SerialPrint("I", F("NextionUpdate"), "connection failed "); + if (!http.begin(_host, 80, _url)) + SerialPrint("I", F("NextionUpdate"), "connection failed "); #elif defined ESP32 - if (!http.begin(String("http://") + _host + _url)) - SerialPrint("I", F("NextionUpdate"), "connection failed "); + if (!http.begin(String("http://") + _host + _url)) + SerialPrint("I", F("NextionUpdate"), "connection failed "); #endif - SerialPrint("I", F("NextionUpdate"), "Requesting file: " + (String)_url); - int code = http.GET(); - // Update the nextion display - if (code == 200) - flashNextion(http); - else - SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str()); + SerialPrint("I", F("NextionUpdate"), "Requesting file: " + (String)_url); + int code = http.GET(); + // Update the nextion display + if (code == 200) + flashNextion(http); + else + SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str()); - http.end(); - SerialPrint("I", F("NextionUpdate"), "Closing connection "); - } + http.end(); + SerialPrint("I", F("NextionUpdate"), "Closing connection "); + } } void uploadNextionTlgrm(String &url) @@ -244,14 +269,14 @@ class Nextion : public IoTUart HTTPClient http; -#ifdef ESP8266 - SerialPrint("I", F("NextionUpdate"), "Update impossible esp8266: Change boards to esp32 :)"); +#ifdef ESP8266 + SerialPrint("I", F("NextionUpdate"), "Update impossible esp8266: Change boards to esp32 :)"); return; -#else +#else if (!http.begin(url)) // пингуем файл SerialPrint("I", F("NextionUpdate"), "connection failed "); #endif - SerialPrint("I", F("NextionUpdate"), "Requesting file: OK" ); + SerialPrint("I", F("NextionUpdate"), "Requesting file: OK"); int code = http.GET(); // Update the nextion display if (code == 200) @@ -259,7 +284,7 @@ class Nextion : public IoTUart else SerialPrint("I", F("NextionUpdate"), "HTTP error: " + (String)http.errorToString(code).c_str()); - http.end(); + //http.end(); SerialPrint("I", F("NextionUpdate"), "Closing connection "); } } @@ -269,35 +294,57 @@ class Nextion : public IoTUart int contentLength = http.getSize(); SerialPrint("I", F("NextionUpdate"), "File received. Update Nextion... "); bool result; - ESPNexUpload nexUp(_speed, _line, _rx, _tx); - nexUp.setUpdateProgressCallback([]() - { SerialPrint("I", F("NextionUpdate"), "... "); }); + ESPNexUpload nexUp(_line, _speed, (gpio_num_t)_tx, (gpio_num_t)_rx); + // nexUp.setUpdateProgressCallback([]() + // { SerialPrint("I", F("NextionUpdate"), "... "); }); - result = nexUp.prepareUpload(contentLength); + result = nexUp.prepareUpload(contentLength, _oldProt); if (!result) { - SerialPrint("I", F("NextionUpdate"), "Error: " + (String)nexUp.statusMessage); + SerialPrint("I", F("NextionUpdate"), "Error Connect in prepare upload"); } else { + updated = true; SerialPrint("I", F("NextionUpdate"), "Start upload. File size is: " + (String)contentLength); result = nexUp.upload(*http.getStreamPtr()); if (result) { - updated = true; + SerialPrint("I", F("NextionUpdate"), "Succesfully updated Nextion! "); + if (tlgrmItem) + tlgrmItem->sendTelegramMsg(false, String("NextionUpdate: Succesfully updated Nextion!")); } else { - SerialPrint("I", F("NextionUpdate"), "Error updating Nextion: " + (String)nexUp.statusMessage); + SerialPrint("I", F("NextionUpdate"), "Error updating Nextion!"); + if (tlgrmItem) + tlgrmItem->sendTelegramMsg(false, String("NextionUpdate: Error updating Nextion!")); } nexUp.end(); + +#ifdef ESP8266 + _myUART->begin(_speed); +#endif +#ifdef ESP32 + if (_line >= 0) + { + //_myUART = new HardwareSerial(_line); + ((HardwareSerial *)_myUART)->updateBaudRate(_speed); + } + else + { + //_myUART = new SoftwareSerial(_rx, _tx); + ((SoftwareSerial *)_myUART)->begin(_speed); + } +#endif + + updated = false; } } -//---------------------NEXTION-UPDATE---END------------------------ + //---------------------NEXTION-UPDATE---END------------------------ ~Nextion(){}; - }; void *getAPI_Nextion(String subtype, String param) diff --git a/src/modules/display/Nextion/modinfo.json b/src/modules/display/Nextion/modinfo.json index 7168875d..be965fb7 100644 --- a/src/modules/display/Nextion/modinfo.json +++ b/src/modules/display/Nextion/modinfo.json @@ -17,7 +17,8 @@ "rx": 16, "line": 2, "speed": 9600, - "uploadTelegram": 1 + "uploadTelegram": 1, + "oldProt_v11": 0 } ], "about": { @@ -28,8 +29,8 @@ "moduleName": "Nextion", "moduleVersion": "2.0", "usedRam": { - "esp32_4mb": 15, - "esp8266_4mb": 15 + "esp32_4mb": 152, + "esp8266_4mb": 152 }, "title": "Nextion", "moduleDesc": "загрузка прошивки в дисплей Nextion. Команда для запуска обновления дисплея: Nextion.Update(); ", @@ -41,6 +42,7 @@ "host": "Сервер обновления. Можно использовать LiveServer из VisualCode, указывать ip адрес", "url": "файл прошивки экрана, указывать с расширением, например nextion.tft или iotm/test.tft", "uploadTelegram": "1 - разрешает прошивать экран через модуль Telegram_v2", + "oldProt_v11": "0 - По умолчанию используется более быстрый протокол версии 1.2 (не официальный), 1 - Использовать старый протокол версии 1.1 для прошивки экрана.", "btn-uploadServer": "Кнопка загрузки прошивки с сервера LiveServer или другого по ip" }, "funcInfo": [ diff --git a/src/modules/display/NextionUpload/NextionUpload.cpp b/src/modules/display/NextionUpload/NextionUpload.cpp index b8745217..f2db9474 100644 --- a/src/modules/display/NextionUpload/NextionUpload.cpp +++ b/src/modules/display/NextionUpload/NextionUpload.cpp @@ -42,8 +42,7 @@ class NextionUpload : public IoTItem HTTPClient http; #if defined ESP8266 - WiFiClient client; - if (!http.begin(client, _host, 80, _url)) + if (!http.begin(_host, 80, _url)) { // Serial.println("connection failed"); SerialPrint("I", F("NextionUpdate"), "connection failed "); @@ -119,13 +118,7 @@ class NextionUpload : public IoTItem int contentLength = http.getSize(); SerialPrint("I", F("NextionUpdate"), "File received. Update Nextion... "); bool result; - #ifdef ESP8266 - ESPNexUpload nextion(115200, -1, _NEXT_RX, _NEXT_TX); - #elif defined(esp32c3m_4mb) || defined(esp32s2_4mb) - ESPNexUpload nextion(115200, 1, _NEXT_RX, _NEXT_TX); - #else - ESPNexUpload nextion(115200, 2, _NEXT_RX, _NEXT_TX); - #endif + ESPNexUpload nextion(115200, _NEXT_RX, _NEXT_TX); nextion.setUpdateProgressCallback([]() { SerialPrint("I", F("NextionUpdate"), "... "); }); diff --git a/src/modules/display/Oled64/modinfo.json b/src/modules/display/Oled64/modinfo.json index b64b4eef..ee34620c 100644 --- a/src/modules/display/Oled64/modinfo.json +++ b/src/modules/display/Oled64/modinfo.json @@ -75,7 +75,7 @@ } ] }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "https://github.com/stblassitude/Adafruit_SSD1306_Wemos_OLED", diff --git a/src/modules/display/Smi2_m/Smi2_m.cpp b/src/modules/display/Smi2_m/Smi2_m.cpp index a93572cc..1d0194c9 100644 --- a/src/modules/display/Smi2_m/Smi2_m.cpp +++ b/src/modules/display/Smi2_m/Smi2_m.cpp @@ -45,7 +45,7 @@ class Smi2_m : public IoTItem { // Пакет,SLAVE адрес,функция модбус,адрес регистра,количесво запрашиваемых регистров,локальный адрес регистра. // Пакет,SLAVE адрес,функция модбус,адрес регистра,данные,локальный адрес регистра. smi->modbus_construct(&packets[PACKET1], 1, PRESET_MULTIPLE_REGISTERS, 4200, 1, 0); - smi->modbus_configure(&Serial, _baud, SERIAL_8N1, _rx, _tx, _pin, packets, TOTAL_NO_OF_PACKETS, regs); + smi->modbus_configure((HardwareSerial*)&Serial, _baud, SERIAL_8N1, _rx, _tx, _pin, &packets[PACKET1], (uint)TOTAL_NO_OF_PACKETS, ®s[0]); jsonRead(parameters, "id2show", _show); } diff --git a/src/modules/display/Smi2_m/modinfo.json b/src/modules/display/Smi2_m/modinfo.json index 443d5c46..e835ce90 100644 --- a/src/modules/display/Smi2_m/modinfo.json +++ b/src/modules/display/Smi2_m/modinfo.json @@ -50,7 +50,7 @@ } ] }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [] } diff --git a/src/modules/display/TM16XX/example.json b/src/modules/display/TM16XX/example.json index 68043ab5..f5effbb6 100644 --- a/src/modules/display/TM16XX/example.json +++ b/src/modules/display/TM16XX/example.json @@ -233,7 +233,8 @@ if tm_17 == 2 then tm.setParamLED(1, 0) if timer1 >= 0 & page == 1 then tm = "ttt1" + timer1 if timer2 >= 0 & page == 2 then tm = "ttt2" + timer2 -if timer1 >= 0 | timer2 >= 0 then { +if timer1 >= 0 | timer2 >= 0 then +{ if page == 3 then tm = timer1 + " " + timer2 } if page == 0 then tm.setParamLED(0, 5) diff --git a/src/modules/display/U8g2lib.zip b/src/modules/display/U8g2lib.zip new file mode 100644 index 00000000..aacb2535 Binary files /dev/null and b/src/modules/display/U8g2lib.zip differ diff --git a/src/modules/display/U8g2lib/DisplayTypes.h b/src/modules/display/U8g2lib/DisplayTypes.h new file mode 100644 index 00000000..4288dc75 --- /dev/null +++ b/src/modules/display/U8g2lib/DisplayTypes.h @@ -0,0 +1,631 @@ +#pragma once +#include "Global.h" +#include +#include +#include + +// #define DEBUG_DISPLAY + +#define DEFAULT_PAGE_UPDATE_ms 500 +// #define DEFAULT_PAGE_TIME_ms 5000 +// #define DEFAULT_ROTATION 0 +// #define DEFAULT_CONTRAST 10 +#define MIN_CONTRAST 10 +#define MAX_CONTRAST 150 + +#ifndef DEBUG_DISPLAY +#define D_LOG(fmt, ...) \ + do { \ + (void)0; \ + } while (0) +#else +#define D_LOG(fmt, ...) Serial.printf((PGM_P)PSTR(fmt), ##__VA_ARGS__) +#endif + +enum rotation_t : uint8_t { + ROTATION_NONE, + ROTATION_90, + ROTATION_180, + ROTATION_270 +}; + +uint8_t parse_contrast(int val) { + if (val < MIN_CONTRAST) val = MIN_CONTRAST; + if (val > MAX_CONTRAST) val = MAX_CONTRAST; + return val; +}; + +rotation_t parse_rotation(int val) { + if ((val > 0) && (val <= 90)) return ROTATION_90; + if ((val > 90) && (val <= 180)) return ROTATION_180; + if ((val > 180) && (val <= 270)) return ROTATION_270; + return ROTATION_NONE; +}; + +struct DisplayPage { + String key; + uint16_t time; + rotation_t rotate; + String font; + String format; + String valign; + + DisplayPage( + const String& key, + uint16_t time, + rotation_t rotate, + const String& font, + const String& format, + const String& valign) : key{key}, time{time}, rotate{rotate}, font{font}, format{format}, valign{valign} {} + + // void load(const JsonObject& obj) { + // // time = obj["time"].as(); + // // rotate = parse_rotation(obj["rotate"].as()); + // // font = obj["font"].as(); + // // valign = obj["valign"].as(); + // // format = obj["format"].as(); + // } + + // auto item = DisplayPage( pageObj["key"].as(), _update, _rotate, _font); + // // Загрузка настроек страницы + // item.load(pageObj); + // page.push_back(item); + + +}; + +enum position_t { + POS_AUTO, + POS_ABSOLUTE, + POS_RELATIVE, + POS_TEXT +}; + +struct RelativePosition { + float x; + float y; +}; + +struct TextPosition { + uint8_t row; + uint8_t col; +}; + +struct Point { + uint16_t x; + uint16_t y; + + Point() : Point(0, 0) {} + + Point(uint16_t x, uint16_t y) : x{x}, y{y} {} + + Point(const Point& rhv) : Point(rhv.x, rhv.y) {} +}; + +struct Position { + position_t type; + union { + Point abs; + RelativePosition rel; + TextPosition text; + }; + + Position() : type{POS_AUTO} {} + + Position(const Point& pos) : type{POS_ABSOLUTE} { + abs.x = pos.x; + abs.y = pos.y; + } + + Position(const RelativePosition& pos) : type{POS_RELATIVE} { + rel.x = pos.x; + rel.y = pos.y; + } + + Position(const TextPosition& pos) : type{POS_TEXT} { + text.col = pos.col; + text.row = pos.row; + } + + Position(const Position& rhv) : type{rhv.type} { + switch (type) { + case POS_ABSOLUTE: + abs = rhv.abs; + case POS_RELATIVE: + rel = rhv.rel; + case POS_TEXT: + text = rhv.text; + default: + break; + } + } +}; + +class Cursor : public Printable { + private: + Point _size; + + public: + TextPosition pos{0, 0}; + Point abs{0, 0}; + Point chr; + Cursor(){}; + + Cursor(const Point& size, const Point& chr) : _size{size}, chr{chr} { + D_LOG("w: %d, h: %d, ch: %d(%d)\r\n", _size.x, _size.y, chr.x, chr.y); + } + + void reset() { + pos.col = 0; + pos.row = 0; + abs.x = 0; + abs.y = 0; + } + + void lineFeed() { + pos.col = 0; + pos.row++; + abs.x = 0; + abs.y += chr.y; + } + + void moveX(uint8_t x) { + abs.x += x; + pos.col = abs.x / chr.x; + } + + void moveY(uint8_t y) { + abs.y += y; + } + + void moveXY(uint8_t x, uint8_t y) { + moveX(x); + moveY(y); + } + + void moveCarret(uint8_t col) { + pos.col += col; + moveX(col * chr.x); + } + + bool isEndOfPage(uint8_t rows = 1) { + return (abs.y + (rows * chr.y)) > _size.y; + } + + bool isEndOfLine(uint8_t cols = 1) { + return (abs.x + (cols * chr.x)) > _size.x; + } + + size_t printTo(Print& p) const { + return p.printf("(c:%d, r:%d x:%d, y:%d)", pos.col, pos.row, abs.x, abs.y); + } +}; + + +struct DisplayHardwareSettings { + int update = DEFAULT_PAGE_UPDATE_ms; + rotation_t rotate; + String font; + int pageTime; + String pageFormat; + int contrast; + bool autoPage; + String valign; +}; + +class Display { + private: + unsigned long _lastResfresh{0}; + Cursor _cursor; + U8G2 *_obj{nullptr}; + DisplayHardwareSettings *_settings; + + public: + Display(U8G2 *obj, DisplayHardwareSettings *settings) : _obj{obj}, _settings(settings) { + _obj->begin(); + _obj->enableUTF8Print(); + _obj->setContrast(_settings->contrast); + setFont(settings->font); + setRotation(settings->rotate); + clear(); + } + + ~Display () { + if (_obj) { + delete _obj; + _obj = nullptr; + } + } + + void setRotation(rotation_t rotate) { + switch (rotate) { + case ROTATION_NONE: + _obj->setDisplayRotation(U8G2_R0); + break; + case ROTATION_90: + _obj->setDisplayRotation(U8G2_R1); + break; + case ROTATION_180: + _obj->setDisplayRotation(U8G2_R2); + break; + case ROTATION_270: + _obj->setDisplayRotation(U8G2_R3); + break; + } + } + + void setFont(const String &fontName = "") { + if (fontName.isEmpty()) { + Display::setFont(_settings->font); + return; + } + + if (fontName.startsWith("c6x12")) + _obj->setFont(u8g2_font_6x12_t_cyrillic); + else if (fontName.startsWith("s6x12")) + _obj->setFont(u8g2_font_6x12_t_symbols); + + else if (fontName.startsWith("c6x13")) + _obj->setFont(u8g2_font_6x13_t_cyrillic); + + else if (fontName.startsWith("c7x13")) + _obj->setFont(u8g2_font_7x13_t_cyrillic); + else if (fontName.startsWith("s7x13")) + _obj->setFont(u8g2_font_7x13_t_symbols); + + else if (fontName.startsWith("c8x13")) + _obj->setFont(u8g2_font_8x13_t_cyrillic); + else if (fontName.startsWith("s8x13")) + _obj->setFont(u8g2_font_8x13_t_symbols); + + else if (fontName.startsWith("c9x15")) + _obj->setFont(u8g2_font_9x15_t_cyrillic); + else if (fontName.startsWith("s9x15")) + _obj->setFont(u8g2_font_9x15_t_symbols); + + else if (fontName.startsWith("c10x20")) + _obj->setFont(u8g2_font_10x20_t_cyrillic); + else if (fontName.startsWith("unifont")) + _obj->setFont(u8g2_font_unifont_t_symbols); + else if (fontName.startsWith("siji")) + _obj->setFont(u8g2_font_siji_t_6x10); + else + _obj->setFont(u8g2_font_6x12_t_cyrillic); + + _cursor.chr.x = getMaxCharHeight(); + // _cursor.chr.y = getLineHeight(); + } + + void initCursor() { + _cursor = Cursor( + {getWidth(), getHeight()}, + {getMaxCharHeight(), getLineHeight()}); + } + + void getPosition(const TextPosition &a, Point &b) { + b.x = a.col * _cursor.chr.x; + b.y = (a.row + 1) * _cursor.chr.y; + } + + void getPosition(const RelativePosition &a, Point &b) { + b.x = getHeight() * a.x; + b.y = getWidth() * a.y; + } + + void getPosition(const Point &a, TextPosition &b) { + b.row = a.y / getLineHeight(); + b.col = a.x / getMaxCharWidth(); + } + + void getPosition(const RelativePosition &a, TextPosition &b) { + Point tmp; + getPosition(a, tmp); + getPosition(tmp, b); + } + + void draw(const RelativePosition &pos, const String &str) { + Point tmp; + getPosition(pos, tmp); + draw(tmp, str); + } + + void draw(TextPosition &pos, const String &str) { + Point tmp; + getPosition(pos, tmp); + draw(tmp, str); + } + + Cursor *getCursor() { + return &_cursor; + } + + // print меняю cursor + void println(const String &str, bool frame = false) { + print(str, frame); + _cursor.lineFeed(); + } + + void print(const String &str, bool frame = false) { + //Serial.print(_cursor); + // x, y нижний левой + int width = _obj->drawUTF8(_cursor.abs.x, _cursor.abs.y + _cursor.chr.y, str.c_str()); + if (frame) { + int x = _cursor.abs.x - getXSpacer(); + int y = _cursor.abs.y - _cursor.chr.y; + width += (getXSpacer() * 2); + int height = _cursor.chr.y + getYSpacer() * 2; + // x, y верхней левой. длина, высота + _obj->drawFrame(x, y, width, height); + D_LOG("[x:%d y:%d w:%d h:%d]", x, y, width, height); + } + _cursor.moveX(width); + } + + // draw не меняет cursor + void draw(const Point &pos, const String &str) { + Serial.printf("(x:%d,y:%d) %s", pos.x, pos.y, str.c_str()); + _obj->drawStr(pos.x, pos.y, str.c_str()); + } + + uint8_t getLineHeight() { + return getMaxCharHeight() + getYSpacer(); + } + + int getXSpacer() { + int res = getWidth() / 100; + if (!res) res = 1; + return res; + } + + int getYSpacer() { + int res = (getHeight() - (getLines() * getMaxCharHeight())) / getLines(); + if (!res) res = 1; + return res; + } + + uint8_t getWidth() { + return _obj->getDisplayWidth(); + } + + uint8_t getHeight() { + return _obj->getDisplayHeight(); + } + + uint8_t getLines() { + uint8_t res = getHeight() / _obj->getMaxCharHeight(); + if (!res) res = 1; + return res; + } + + uint8_t getMaxCharHeight() { + return _obj->getMaxCharHeight(); + } + + uint8_t getMaxCharWidth() { + return _obj->getMaxCharWidth(); + } + + void clear() { + _obj->clearDisplay(); + _cursor.reset(); + } + + void startRefresh() { + _obj->clearBuffer(); + _cursor.reset(); + } + + void endRefresh() { + _obj->sendBuffer(); + _lastResfresh = millis(); + } + + bool isNeedsRefresh() { + // SerialPrint("[Display]", "_settings->update: " + String(_settings->update) + "ms", ""); + return !_lastResfresh || (millis() > (_lastResfresh + _settings->update)); + } +}; + +struct ParamPropeties { + // рамка + bool frame[false]; +}; + +struct Param { + // Ключ + const String key; + // Префикс к значению + String pref; + // Суффикс к значению + String suff; + // Значение + String value; + + String pref_fnt; + String suff_fnt; + String value_fnt; + + String gliphs; + + // значение изменилось + bool updated; + // группа + uint8_t group; + ParamPropeties props; + Position position; + + Param(const String &key, + const String &pref = emptyString, const String &value = emptyString, const String &suff = emptyString, + const String &pref_fnt = emptyString, const String &value_fnt = emptyString, const String &suff_fnt = emptyString, + const String &gliphs = emptyString + ) : key{key}, group{0} { + setValue(value.c_str()); + setPref(pref); + setSuff(suff); + this->pref_fnt = pref_fnt; + this->value_fnt = value_fnt; + this->suff_fnt = suff_fnt; + this->gliphs = gliphs; + updated = false; + } + + bool isValid() { + return !pref.isEmpty(); + } + + bool setPref(const String &str) { + if (!pref.equals(str)) { + pref = str; + updated = true; + return true; + } + return false; + } + + bool setSuff(const String &str) { + if (!suff.equals(str)) { + suff = str; + updated = true; + return true; + } + return false; + } + + bool setValue(const String &str) { + if (!value.equals(str)) { + value = str; + updated = true; + return true; + } + return false; + } + + + + void draw(Display *obj, uint8_t line) { + } + + void draw(Display *obj) { + auto type = position.type; + switch (type) { + case POS_AUTO: { + D_LOG("AUTO %s '%s%s'\r\n", key.c_str(), descr.c_str(), value.c_str()); + obj->setFont(pref_fnt); + obj->print(pref.c_str()); + + obj->setFont(value_fnt); + obj->println(value.c_str(), false); + + obj->setFont(suff_fnt); + obj->print(suff.c_str()); + } + case POS_ABSOLUTE: { + auto pos = position.abs; + D_LOG("ABS(%d, %d) %s %s'\r\n", pos.x, pos.y, key.c_str(), value.c_str()); + obj->draw(pos, value); + } + case POS_RELATIVE: { + auto pos = position.rel; + D_LOG("REL(%2.2f, %2.2f) %s %s'\r\n", pos.x, pos.y, key.c_str(), value.c_str()); + obj->draw(pos, value); + } + case POS_TEXT: { + auto pos = position.text; + D_LOG("TXT(%d, %d) %s %s'\r\n", pos.col, pos.row, key.c_str(), value.c_str()); + obj->draw(pos, value); + } + default: + D_LOG("unhadled: %d", type); + } + } +}; + +class ParamCollection { + std::vector _item; + + public: + void load() { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { + if ((*it)->getSubtype() == "" || (*it)->getSubtype() == "U8g2lib") continue; + + auto entry = find((*it)->getID()); + if (!entry) { + _item.push_back({(*it)->getID(), (*it)->getID() + ": ", (*it)->getValue(), "", "", "", ""}); + } else { + entry->setValue((*it)->getValue()); + if (entry->pref == "") + entry->setPref((*it)->getID() + ": "); + } + } + } + + void loadExtParamData(String parameters) { + String id = ""; + jsonRead(parameters, "id", id, false); + if (id != "") { + String pref = ""; + String suff = ""; + String pref_fnt = ""; + String suff_fnt = ""; + String value_fnt = ""; + String gliphs = ""; + + bool hasExtParam = false; + + hasExtParam = hasExtParam + jsonRead(parameters, "pref", pref, false); + hasExtParam = hasExtParam + jsonRead(parameters, "suff", suff, false); + hasExtParam = hasExtParam + jsonRead(parameters, "pref_fnt", pref_fnt, false); + hasExtParam = hasExtParam + jsonRead(parameters, "suff_fnt", suff_fnt, false); + hasExtParam = hasExtParam + jsonRead(parameters, "value_fnt", value_fnt, false); + hasExtParam = hasExtParam + jsonRead(parameters, "gliphs", gliphs, false); + + if (hasExtParam) { + _item.push_back({id, pref, "", suff, pref_fnt, value_fnt, suff_fnt, gliphs}); + } + } + } + + Param *find(const String &key) { + return find(key.c_str()); + } + + Param *find(const char *key) { + Param *res = nullptr; + for (size_t i = 0; i < _item.size(); i++) { + if (_item.at(i).key.equalsIgnoreCase(key)) { + res = &_item.at(i); + break; + } + } + return res; + } + + Param *get(int n) { + return &_item.at(n); + } + + size_t count() { + return _item.size(); + } + + // n - номер по порядку параметра + Param *getValid(int n) { + for (size_t i = 0; i < _item.size(); i++) + if (_item.at(i).isValid()) + if (!(n--)) return &_item.at(i); + return nullptr; + } + + size_t getVaildCount() { + size_t res = 0; + for (auto entry : _item) res += entry.isValid(); + return res; + } + + size_t max_group() { + size_t res = 0; + for (auto entry : _item) + if (res < entry.group) res = entry.group; + return res; + } +}; \ No newline at end of file diff --git a/src/modules/display/U8g2lib/U8g2lib.cpp b/src/modules/display/U8g2lib/U8g2lib.cpp new file mode 100644 index 00000000..97dc291f --- /dev/null +++ b/src/modules/display/U8g2lib/U8g2lib.cpp @@ -0,0 +1,419 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include +#include "DisplayTypes.h" + +#define STRHELPER(x) #x +#define TO_STRING_AUX(...) "" #__VA_ARGS__ +#define TO_STRING(x) TO_STRING_AUX(x) + + +// дополненный список параметров для вывода, который синхронизирован со списком значений IoTM +ParamCollection *extParams{nullptr}; + +// класс одного главного экземпляра экрана для выделения памяти только когда потребуется экран +class DisplayImplementation { + private: + unsigned long _lastPageChange{0}; + bool _pageChanged{false}; + // uint8_t _max_descr_width{0}; + // typedef std::vector Line; + // текущая + size_t _page_n{0}; + // struct Page { + // std::vector line; + // }; + + uint8_t _n{0}; // последний отображенный + + DisplayHardwareSettings *_context{nullptr}; + Display *_display{nullptr}; + + public: + DisplayImplementation(DisplayHardwareSettings *context = nullptr, + Display *display = nullptr) + : _context(context), _display(display) { + + } + + ~DisplayImplementation() { + if (_display) { + delete _display; + _display = nullptr; + } + if (_context) { + delete _context; + _context = nullptr; + } + if (extParams) { + delete extParams; + extParams = nullptr; + } + } + + std::vector page; + + void nextPage() { + _n = _n + 1; + if (_n == page.size()) _n = _n - 1; + _pageChanged = true; + } + + void prevPage() { + if (_n > 0) _n = _n - 1; + _pageChanged = true; + } + + void rotPage() { + _n = _n + 1; + if (_n == page.size()) _n = 0; + _pageChanged = true; + } + + void gotoPage(uint8_t num) { + _n = num; + if (num < 0) _n = 0; + if (num >= page.size()) _n = page.size() - 1; + _pageChanged = true; + } + + void setAutoPage(bool isAuto) { + if (_context) _context->autoPage = isAuto; + _pageChanged = true; + } + + uint8_t calcPageCount(ParamCollection *param, uint8_t linesPerPage) { + size_t res = 0; + size_t totalLines = param->count(); + if (totalLines && linesPerPage) { + res = totalLines / linesPerPage; + if (totalLines % linesPerPage) res++; + } + return res; + } + + // uint8_t getPageCount() { + // return isAutoPage() ? calcPageCount(_param, _display->getLines()) : getPageCount(); + // } + + // выводит на страницу параметры начиная c [n] + // возвращает [n] последнего уместившегося + uint8_t draw(Display *display, ParamCollection *param, uint8_t n) { + // Очищает буфер (не экран, а внутреннее представление) для последущего заполнения + display->startRefresh(); + size_t i = 0; + // вот тут лог ошибка + for (i = n; i < param->count(); i++) { + auto cursor = display->getCursor(); + auto entry = param->get(i); + auto len = entry->value.length() + entry->pref.length() + entry->suff.length() ; + if (cursor->isEndOfLine(len)) cursor->lineFeed(); + + printParam(display, entry, _context->font); + + if (cursor->isEndOfPage(0)) break; + } + // Отправит готовый буфер страницы на дисплей + display->endRefresh(); + return i; + } + + String slice(const String &str, size_t index, char delim) { + size_t cnt = 0; + int subIndex[] = {0, -1}; + size_t maxIndex = str.length() - 1; + + for (size_t i = 0; (i <= maxIndex) && (cnt <= index); i++) { + if ((str.charAt(i) == delim) || (i == maxIndex)) { + cnt++; + subIndex[0] = subIndex[1] + 1; + subIndex[1] = (i == maxIndex) ? i + 1 : i; + } + } + return cnt > index ? str.substring(subIndex[0], subIndex[1]) : emptyString; + } + + void printParam(Display *display, Param *param, const String &parentFont) { + if (!param->pref.isEmpty()) { + display->setFont(param->pref_fnt.isEmpty() ? parentFont : param->pref_fnt); + display->print(param->pref); + } + + if (!param->value.isEmpty()) { + display->setFont(param->value_fnt.isEmpty() ? parentFont : param->value_fnt); + if (!param->gliphs.isEmpty() && isDigitStr(param->value)) { + int glyphIndex = param->value.toInt(); + display->print(getUtf8CharByIndex(param->gliphs, glyphIndex)); + } else display->print(param->value); + } + + if (!param->suff.isEmpty()) { + display->setFont(param->suff_fnt.isEmpty() ? parentFont : param->suff_fnt); + display->print(param->suff); + } + } + + void showXXX(Display *display, ParamCollection *param, uint8_t page) { + size_t linesPerPage = display->getLines(); + size_t line_first = _page_n * linesPerPage; + size_t line_last = line_first + linesPerPage - 1; + + display->startRefresh(); + + size_t lineOfPage = 0; + for (size_t n = line_first; n <= line_last; n++) { + auto entry = param->get(n); + if (entry) { + entry->draw(_display, lineOfPage); + lineOfPage++; + } else { + break; + } + } + display->endRefresh(); + } + + void drawPage(Display *display, ParamCollection *params, DisplayPage *page) { + display->setFont(page->font); + display->initCursor(); + + auto keys = page->key; + D_LOG("page keys: %s\r\n", keys.c_str()); + size_t l = 0; + auto line_keys = slice(keys, l, '#'); + while (!line_keys.isEmpty()) { + if (page->valign.equalsIgnoreCase("center")) { + display->getCursor()->moveY((display->getHeight() / 2) - display->getMaxCharHeight() / 2); + } + D_LOG("line keys: %s\r\n", keys.c_str()); + size_t n = 0; + auto key = slice(line_keys, n, ','); + while (!key.isEmpty()) { + D_LOG("key: %s\r\n", key.c_str()); + auto entry = params->find(key.c_str()); + if (entry && entry->updated) { + if (n) display->print(" "); + printParam(display, entry, page->font); + } + key = slice(line_keys, ++n, ','); + } + display->getCursor()->lineFeed(); + line_keys = slice(keys, ++l, '#'); + } + } + + // Режим пользовательской разбивки параметров по страницам + void showManual(Display *display, ParamCollection *param) { + auto page = getPage(_n); + + if (display->isNeedsRefresh() || _pageChanged) { + D_LOG("[Display] page: %d\r\n", _n); + display->setRotation(page->rotate); + display->startRefresh(); + drawPage(display, param, page); + display->endRefresh(); + _pageChanged = false; + } + + if (_context->autoPage && millis() >= (_lastPageChange + page->time)) { + // Если это была последняя начинаем с начала + if (++_n > (getPageCount() - 1)) _n = 0; + _pageChanged = true; + _lastPageChange = millis(); + } + } + + // Режим авто разбивки параметров по страницам + void showAuto(Display *display, ParamCollection *param) { + size_t param_count = param->count(); + + if (!param_count) return; + + display->setFont(_context->font); + display->initCursor(); + + size_t last_n = _n; + if (display->isNeedsRefresh() || _pageChanged) { + //D_LOG("n: %d/%d\r\n", _n, param_count); + last_n = draw(display, param, _n); + } + + if (_context->autoPage && millis() >= (_lastPageChange + _context->pageTime)) { + _n = last_n; + if (_n >= param_count) _n = 0; + _pageChanged = true; + _lastPageChange = millis(); + } + } + + void show() { + if (extParams && _display) { + extParams->load(); + + if (isAutoPage()) { + showAuto(_display, extParams); + } else { + showManual(_display, extParams); + } + } + } + + bool isAutoPage() { + return !getPageCount(); + } + + uint8_t getPageCount() { + return page.size(); + } + + DisplayPage* getPage(uint8_t index) { + return &page.at(index); + } +}; + + +DisplayImplementation* displayImpl = nullptr; + + +class U8g2lib : public IoTItem { + private: + uint8_t _pageNum = 0; + + public: + U8g2lib(String parameters) : IoTItem(parameters) { + DisplayHardwareSettings *context = new DisplayHardwareSettings(); + if (!context) { + D_LOG("[Display] disabled"); + return; + } + + jsonRead(parameters, "update", context->update); + jsonRead(parameters, "font", context->font); + + int rotate; + jsonRead(parameters, "rotation", rotate); + context->rotate = parse_rotation(rotate); + + jsonRead(parameters, "contrast", context->contrast); + jsonRead(parameters, "autoPage", context->autoPage); + jsonRead(parameters, "pageTime", context->pageTime); + + bool itsFirstDisplayInit = false; + if (!displayImpl) { + // Значит это первый элемент U8g2lib в конфигурации - Инициализируем дисплей + itsFirstDisplayInit = true; + int dc = U8X8_PIN_NONE, cs = U8X8_PIN_NONE, data = U8X8_PIN_NONE, clock = U8X8_PIN_NONE, rst = U8X8_PIN_NONE; + jsonRead(parameters, "dc", dc); + jsonRead(parameters, "cs", cs); + jsonRead(parameters, "data", data); + jsonRead(parameters, "clock", clock); + jsonRead(parameters, "rst", rst); + if (dc == -1) dc = U8X8_PIN_NONE; + if (cs == -1) cs = U8X8_PIN_NONE; + if (data == -1) data = U8X8_PIN_NONE; + if (clock == -1) clock = U8X8_PIN_NONE; + if (rst == -1) rst = U8X8_PIN_NONE; + + String type; + jsonRead(parameters, "oledType", type); + U8G2* libObj = nullptr; + if (type.startsWith("ST")) { + libObj = new U8G2_ST7565_ERC12864_F_4W_SW_SPI(U8G2_R0, clock, data, cs, dc, rst); + } + else if (type.startsWith("SS_I2C")) { + // libObj = new U8G2_SSD1306_128X64_VCOMH0_F_SW_I2C(U8G2_R0, clock, data, rst); + libObj = new U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C(U8G2_R0, clock, data, rst); + + } + else if (type.startsWith("SS_SPI")) { + libObj = new U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI(U8G2_R0, clock, data, cs, dc, rst); + } + else if (type.startsWith("SH")) { + libObj = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, rst, clock, data); + } + + if (!libObj) { + D_LOG("[Display] disabled"); + return; + } + + Display *_display = new Display(libObj, context); + if (!_display) { + D_LOG("[Display] disabled"); + return; + } + + if (!extParams) extParams = new ParamCollection(); + + displayImpl = new DisplayImplementation(context, _display); + if (!displayImpl) { + D_LOG("[Display] disabled"); + return; + } + } + + // добавляем страницу, если указан ID для отображения + String id2show; + jsonRead(parameters, "id2show", id2show); + if (!id2show.isEmpty()) { + auto item = DisplayPage( + id2show, + context->pageTime, + context->rotate, + context->font, + context->pageFormat, + context->valign + ); + _pageNum = displayImpl->page.size(); + displayImpl->page.push_back(item); + if (!itsFirstDisplayInit) delete context; // если это не первый вызов, то контекст имеет временный характер только для создания страницы + } + } + + void doByInterval() { + if (displayImpl) displayImpl->show(); + } + + IoTValue execute(String command, std::vector& param) { + if (displayImpl) + if (command == "nextPage") { + displayImpl->nextPage(); + } else if (command == "prevPage") { + displayImpl->prevPage(); + } else if (command == "rotPage") { + displayImpl->rotPage(); + } else if (command == "gotoPage") { + if (param.size() == 1) { + displayImpl->gotoPage(param[0].valD); + } else { + displayImpl->gotoPage(_pageNum); + } + } else if (command == "setAutoPage") { + if (param.size() == 1) { + displayImpl->setAutoPage(param[0].valD); + } + } + + return {}; + } + + ~U8g2lib() { + if (displayImpl) { + delete displayImpl; + displayImpl = nullptr; + } + }; +}; + +void* getAPI_U8g2lib(String subtype, String param) { + if (subtype == F("U8g2lib")) { + // SerialPrint("[Display]", "param1: ", param); + return new U8g2lib(param); + } else { + // элемент не наш, но проверяем на налличие модификаторов, которые нужны для модуля + // вынимаем ID элемента и значения pref и suff связанные с ним + if (!extParams) extParams = new ParamCollection(); + extParams->loadExtParamData(param); + return nullptr; + } +} diff --git a/src/modules/display/U8g2lib/example_config.json b/src/modules/display/U8g2lib/example_config.json new file mode 100644 index 00000000..48809c20 --- /dev/null +++ b/src/modules/display/U8g2lib/example_config.json @@ -0,0 +1,217 @@ +{ + "mark": "iotm", + "config": [ + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "btn", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "ТестКнопка", + "int": "0", + "val": "0", + "value_fnt": "siji", + "gliphs": "" + }, + { + "global": 0, + "type": "Writing", + "subtype": "Timer", + "id": "timer", + "widget": "anydataDef", + "page": "Ввод", + "descr": "Таймер", + "int": 1, + "countDown": "99", + "ticker": 1, + "repeat": 1, + "needSave": 0, + "pref": "ТАЙМЕР: ", + "suff": " сек", + "round": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "time", + "needSave": 0, + "widget": "anydataRed", + "page": "Ввод", + "descr": "Время", + "int": "0", + "val": "", + "pref": " ⏰️", + "pref_fnt": "unifont" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "var", + "needSave": 0, + "widget": "inputTxt", + "page": "Ввод", + "descr": "Текст", + "int": "0", + "val": "☀️-☁️-☂️-☃️-☄️", + "map": "1024,1024,1,100", + "plus": 0, + "multiply": 1, + "round": 0, + "pref": "текст: ", + "value_fnt": "unifont" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "ip", + "needSave": 0, + "widget": "anydataDef", + "page": "Ввод", + "descr": "IP", + "int": "0", + "val": "", + "pref": "IP: " + }, + { + "type": "Reading", + "subtype": "U8g2lib", + "id": "page1", + "widget": "nil", + "page": "", + "descr": "", + "oledType": "SS_I2C", + "int": "1", + "font": "c6x13", + "contrast": "200", + "rotation": "0", + "autoPage": "0", + "pageTime": "10000", + "dc": 19, + "cs": "-1", + "data": "21", + "clock": "22", + "rst": -1, + "id2show": "timer,lvl#ip" + }, + { + "type": "Reading", + "subtype": "U8g2lib", + "id": "page2", + "widget": "nil", + "page": "", + "descr": "", + "oledType": "SS_I2C", + "int": 1, + "update": 500, + "font": "c6x13", + "contrast": "150", + "rotation": "0", + "autoPage": "0", + "pageTime": 3000, + "id2show": "var#btn,time", + "dc": "-1", + "cs": "-1", + "data": "-1", + "clock": "-1", + "rst": -1 + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "autoPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "autoPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "nextPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "nextPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "prevPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "prevPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "pageN", + "needSave": 0, + "widget": "inputDgt", + "page": "Ввод", + "descr": "pageN", + "int": "0", + "val": "0.0", + "map": "1024,1024,1,100", + "plus": 0, + "multiply": 1, + "round": 0 + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "rotPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "rotPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "AnalogAdc", + "id": "lvl", + "widget": "anydataRed", + "page": "Ввод", + "descr": "Уровень", + "map": "1,1024,1,5", + "plus": 0, + "multiply": 1, + "round": "0", + "pin": "34", + "int": "1", + "avgSteps": 1, + "pref": " ", + "value_fnt": "siji", + "gliphs": "" + } + ] +} + +scenario=>if timer then { +ip = getIP() +time = gethhmmss() +} +if autoPage then page1.setAutoPage(1) else page1.setAutoPage(0) +if nextPage < 2 then page1.nextPage() +if prevPage < 2 then page1.prevPage() +if rotPage < 2 then page1.rotPage() +if pageN != "" then page1.gotoPage(pageN) \ No newline at end of file diff --git a/src/modules/display/U8g2lib/modinfo.json b/src/modules/display/U8g2lib/modinfo.json new file mode 100644 index 00000000..d4e18bb3 --- /dev/null +++ b/src/modules/display/U8g2lib/modinfo.json @@ -0,0 +1,96 @@ +{ + "menuSection": "screens", + "configItem": [ + { + "name": "Экраны U8g2", + "type": "Reading", + "subtype": "U8g2lib", + "id": "u8page", + "widget": "", + "page": "", + "descr": "", + + "oledType": "SS_I2C", + "int": 1, + "update": 500, + "font": "c6x13", + "contrast": 90, + "rotation": 90, + "autoPage": 1, + "pageTime": 3000, + "id2show": "", + + "dc": 19, + "cs": 5, + "data": 23, + "clock": 18, + "rst": -1 + } + ], + "about": { + "authorName": "Ilya Belyakov", + "authorContact": "https://t.me/Biveraxe", + "authorGit": "https://github.com/biveraxe", + "specialThanks": "Yuriy Trikoz @ytrikoz", + "moduleName": "U8g2lib", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "moduleDesc": "Позволяет выводить на графические экраны типа SSD, ST, SH указанные параметры из конфигурации IoTM.", + "propInfo": { + "oledType": "Строковый код типа дисплея. В текущей верссии поддерживаются ST7565 (ST), SSD1306 (SS_I2C), SSD1306 (SS_SPI) и SH1106 (SH). Для получения списка доступных типов дисплеев, обратитесь к документации библиотеки U8g2. Добавить возможность выбора типов дисплеев можно, добавив соответствующие условия в файл модуля в конструктор класса U8g2lib.", + "int": "Интервал обновления экрана в секундах. Если указано 0, то обновление экрана не производится.", + "update": "Интервал обновления экрана в миллисекундах. Если указано 0, то обновление экрана не производится. (парамтер на развитие)", + "font": "Шрифт, используемый для отображения текста на экране. Доступные шрифты можно найти в документации библиотеки U8g2 и добавить в проект в функцию setFont().", + "contrast": "Контрастность экрана. Значение от 10 до 150, где 0 - минимальная контрастность, а 255 - максимальная.", + "rotation": "Поворот экрана в градусах. Доступные значения: 0, 90, 180, 270.", + "autoPage": "Автоматическая смена страниц экрана. Если установлено в 1, то экран будет автоматически переключаться на следующую страницу после указанного времени.", + "pageTime": "Время в миллисекундах, через которое будет происходить автоматическая смена страниц экрана. Используется только если autoPage установлено в 1.", + "id2show": "Идентификатор элемента конфигурации, значение которого будет отображаться на экране. Если указано, то на экране будет отображаться только это значение. Возможно указать несколько идентификаторов, разделенных запятыми для перечисления горизонтально и # для перевода строки.", + "dc": "Пин, используемый для управления дисплеем по протоколу I2C. Если не используется, укажите -1.", + "cs": "Пин, используемый для управления дисплеем по протоколу SPI. Если не используется, укажите -1.", + "data": "Пин, используемый для передачи данных на дисплей по протоколу SPI. Если не используется, укажите -1.", + "clock": "Пин, используемый для синхронизации данных на дисплее по протоколу SPI. Если не используется, укажите -1.", + "rst": "Пин, используемый для сброса дисплея. Если не используется, укажите -1." + }, + "title": "Дисплей U8g2lib", + "funcInfo": [ + { + "name": "nextPage", + "descr": "Переключиться на следующую страницу", + "params": [] + }, + { + "name": "prevPage", + "descr": "Переключиться на предыдущую страницу", + "params": [] + }, + { + "name": "rotPage", + "descr": "Переключиться на следующую страницу с ротацией", + "params": [] + }, + { + "name": "gotoPage", + "descr": "Переключиться на указанную страницу. Если номер не указать, то переключится на страницу закрепленную за элементом конфигурации.", + "params": ["Номер страницы"] + }, + { + "name": "setAutoPage", + "descr": "Установить автоматическую смену страниц.", + "params": ["1 - включить, 0 - выключить"] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32*": [ + "olikraus/U8g2 @ ^2.36.5" + ], + "esp82*": [ + "olikraus/U8g2 @ ^2.36.5" + ] + } +} \ No newline at end of file diff --git a/src/modules/exec/AnalogBtn/modinfo.json b/src/modules/exec/AnalogBtn/modinfo.json index ca3d11d2..3b5c7940 100644 --- a/src/modules/exec/AnalogBtn/modinfo.json +++ b/src/modules/exec/AnalogBtn/modinfo.json @@ -38,6 +38,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/exec/BrokerMQTT/BrokerMQTT.cpp b/src/modules/exec/BrokerMQTT/BrokerMQTT.cpp index 9d62d553..91c2b99a 100644 --- a/src/modules/exec/BrokerMQTT/BrokerMQTT.cpp +++ b/src/modules/exec/BrokerMQTT/BrokerMQTT.cpp @@ -7,12 +7,13 @@ namespace _Broker { #define DEF_PORT 1883 +bool _global_debug = false; // MqttBroker broker(1883); class myPicoMQTT : public PicoMQTT::Server { private: - bool _debug; + //bool _debug; String _user; String _pass; @@ -27,15 +28,15 @@ namespace _Broker _pass = pass; } - void setDebug(bool debug) +/* void setDebug(bool debug) { _debug = debug; } - + */ protected: void on_connected(const char *client_id) { - if (_debug) + if (_Broker::_global_debug) { Serial.print("[BrokerMQTT], Client connected: "); Serial.println(client_id); @@ -43,7 +44,7 @@ namespace _Broker } void on_disconnected(const char *client_id) { - if (_debug) + if (_Broker::_global_debug) { // SerialPrint("i", "BrokerMQTT", "Client disconnected: " + client_id); @@ -53,7 +54,7 @@ namespace _Broker } void on_subscribe(const char *client_id, const char *topic) { - if (_debug) + if (_Broker::_global_debug) { // SerialPrint("i", "BrokerMQTT", "Client " + client_id + ", subscribe: " + topic); @@ -65,7 +66,7 @@ namespace _Broker } void on_unsubscribe(const char *client_id, const char *topic) { - if (_debug) + if (_Broker::_global_debug) { // SerialPrint("i", "BrokerMQTT", "Client " + client_id + ", unsubscribe: " + topic); @@ -120,6 +121,8 @@ namespace _Broker clientMqtt->loop(); if (picoMqtt) picoMqtt->loop(); + if (!clientMqtt && !picoMqtt) + vTaskDelete(NULL); // picoMqtt.loop(); // vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(5)); } @@ -152,19 +155,6 @@ namespace _Broker jsonRead(parameters, "srvUser", _srvUser); jsonRead(parameters, "srvPass", _srvPass); jsonRead(parameters, "srvPort", _srvPort); - - if (_brige) - { - clientMqtt = new PicoMQTT::Client(_server.c_str(), _srvPort, nullptr, _srvUser.c_str(), _srvPass.c_str()); - if (_debug) - { - SerialPrint("i", F("BrigeMQTT"), "Bridge mode : ON"); - SerialPrint("i", F("BrigeMQTT"), "Bridge server: " + _server); - SerialPrint("i", F("BrigeMQTT"), "Bridge port: " + String(_srvPort)); - SerialPrint("i", F("BrigeMQTT"), "Bridge user: " + _srvUser); - SerialPrint("i", F("BrigeMQTT"), "Bridge pass: " + _srvPass); - } - } } void doByInterval() @@ -176,13 +166,33 @@ namespace _Broker _port = DEF_PORT; picoMqtt = new myPicoMQTT(_port); picoMqtt->begin(); - picoMqtt->setDebug(_debug); + //picoMqtt->setDebug(_debug); + _global_debug = _debug; picoMqtt->setAuth(_user, _pass); + if (_brige) + { + clientMqtt = new PicoMQTT::Client(_server.c_str(), _srvPort, chipId.c_str(), _srvUser.c_str(), _srvPass.c_str()); + clientMqtt->begin(); + if (_debug) + { + SerialPrint("i", F("BrigeMQTT"), "Bridge mode : ON"); + SerialPrint("i", F("BrigeMQTT"), "Bridge server: " + _server); + SerialPrint("i", F("BrigeMQTT"), "Bridge port: " + String(_srvPort)); + SerialPrint("i", F("BrigeMQTT"), "Bridge user: " + _srvUser); + SerialPrint("i", F("BrigeMQTT"), "Bridge pass: " + _srvPass); + } + } if (_brige && picoMqtt && clientMqtt) { picoMqtt->subscribe("#", [](const char *topic, const char *message) { clientMqtt->publish(topic, message); - SerialPrint("i", F("BrigeMQTT"), "client publish, topic: " + String(topic) + " msg: " + String(message) ); }); + if (_Broker::_global_debug) + SerialPrint("i", F("BrigeMQTT"), "Client publish, topic: " + String(topic) + " msg: " + String(message) ); }); + + clientMqtt->subscribe("#", [](const char *topic, const char *message) + { picoMqtt->publish(topic, message); + if (_Broker::_global_debug) + SerialPrint("i", F("BrigeMQTT"), "Server publish, topic: " + String(topic) + " msg: " + String(message) ); }); } // picoMqtt.begin(); xTaskCreatePinnedToCore( @@ -205,9 +215,11 @@ namespace _Broker ~BrokerMQTT() { - vTaskDelete(brokerTask); - delete picoMqtt; - delete clientMqtt; + //vTaskDelete(brokerTask); + if (picoMqtt) + delete picoMqtt; + if (clientMqtt) + delete clientMqtt; } }; } diff --git a/src/modules/exec/ButtonIn/modinfo.json b/src/modules/exec/ButtonIn/modinfo.json index 07fcc87c..e9c11793 100644 --- a/src/modules/exec/ButtonIn/modinfo.json +++ b/src/modules/exec/ButtonIn/modinfo.json @@ -46,6 +46,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/exec/ButtonOut/modinfo.json b/src/modules/exec/ButtonOut/modinfo.json index d655bed9..7a033c18 100644 --- a/src/modules/exec/ButtonOut/modinfo.json +++ b/src/modules/exec/ButtonOut/modinfo.json @@ -50,6 +50,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/exec/Buzzer/modinfo.json b/src/modules/exec/Buzzer/modinfo.json index be7ee225..3036985c 100644 --- a/src/modules/exec/Buzzer/modinfo.json +++ b/src/modules/exec/Buzzer/modinfo.json @@ -101,7 +101,7 @@ } ] }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [], "esp82*": [] diff --git a/src/modules/exec/EctoControlAdapter/AdapterCommon.h b/src/modules/exec/EctoControlAdapter/AdapterCommon.h new file mode 100644 index 00000000..4b6b7bca --- /dev/null +++ b/src/modules/exec/EctoControlAdapter/AdapterCommon.h @@ -0,0 +1,182 @@ +#pragma once +#include + +#define comm_RebootAdapter 2u +#define comm_LockOutReset 3u + +struct BoilerInfo +{ + uint8_t adapterType; // тип адаптера. 000 - Opentherm 001 - eBus 010 - Navien + uint8_t boilerStatus; // состояние связи с котлом 0 - нет ответа от котла на последнюю команду 1 - есть ответ от котла на последнюю команду + uint8_t rebootStatus; // Код последней перезагрузки адаптера. 0...255 - код + uint8_t adapterHardVer; // Аппаратная версия адаптера. 0...255 - номер версии + uint8_t adapterSoftVer; // u8 Программная версия адаптера. 0...255 - номер версии + uint16_t boilerMemberCode; // 0x0021 R u16 Код производителя котла. Зависит от марки и модели котла. 0...65535 - код производителя + uint16_t boilerModelCode; // 0x0022 R u16 Код модели котла. Зависит от марки и модели котла. 0...65535 - код модели +}; + +struct BoilerStatus +{ + uint8_t burnStatus; // бит 0 - текущее состояние горелки 0 - отключена 1 - включена + uint8_t CHStatus; // бит 1 - текущее состояние отопления 0 - отключено 1 - включено + uint8_t DHWStatus; // бит 2 - текущее состояние ГВС 0 - отключено 1 - включено +}; + +// Флаги ошибок (только для котлов с интерфейсом OpenTherm) +enum FlagErrorOT //: uitn8_t +{ + er_SecviceReq, // 0: Необходимо обслуживание + er_LockOut, // 1: Котел заблокирован + er_LowWater, // 2: Низкое давление в отопительном контуре + er_FlameFault, // 3: Ошибка розжига + er_AirPresFault, // 4: Низкое давление воздуха + er_OverTem // 5: Перегрев теплоносителя в контуре +}; + +////////////////// Данные регистров второй версии адаптера котла +//////////////////// Регистры для чтения + +enum ReadDataEctoControl //: uitn16_t +{ + ecR_AdapterInfo = 0x0010, // 0x0010 R bitfields бит 2...0 - тип адаптера. 000 - Opentherm 001 - eBus 010 - Navien + // бит 3 - состояние связи с котлом 0 - нет ответа от котла на последнюю команду 1 - есть ответ от котла на последнюю команду + // u8 Код последней перезагрузки адаптера. 0...255 - код + + ecR_AdaperVersion = 0x0011, // 0x0011 R u8 Аппаратная версия адаптера. 0...255 - номер версии + // u8 Программная версия адаптера. 0...255 - номер версии + + ecR_Time = 0x0012, // 0x0012 - 0x0013 R u32 Время работы адаптера после перезагрузки 0...4294967295 - время в секундах + ecR_MinSetCH = 0x0014, // 0x0014 R u8 Нижний предел уставки теплоносителя. 0...100 - температура уставки в град. С + ecR_MaxSetCH = 0x0015, // 0x0015 R u8 Верхний предел уставки теплоносителя 0...100 - температура уставки в град. С + ecR_MinSetDHW = 0x0016, // 0x0016 R u8 Нижний предел уставки ГВС 0...100 - температура уставки в град. С + ecR_MaxSetDHW = 0x0017, // 0x0017 R u8 Верхний предел уставки ГВС 0...100 - температура уставки в град. С + ecR_TempCH = 0x0018, // 0x0018 R i16 Текущая температура теплоносителя -100...100 - температура в 0.1 гр. С + ecR_TempDHW = 0x0019, // 0x0019 R u16 Текущая температура ГВС 0...100 - температура в 0.1 гр. С + ecR_Pressure = 0x001A, // 0x001A R u8 Текущее Давление в контуре 0...50 - давление в (0,1бар) + ecR_FlowRate = 0x001B, // 0x001B R u8? Текущий расход ГВС 1...255 - расход в (0,1 л/мин) + ecR_ModLevel = 0x001C, // 0x001C R u8? Текущая модуляция горелки 0xFF - не определено 0...100 - модуляция в (%) + ecR_BoilerStatus = 0x001D, // 0x001D R bitfields бит 0 - текущее состояние горелки 0 - отключена 1 - включена + // бит 1 - текущее состояние отопления 0 - отключено 1 - включено + // бит 2 - текущее состояние ГВС 0 - отключено 1 - включено + + ecR_CodeError = 0x001E, // 0x001E R u16 Код ошибки котла (основной). Зависит от марки и модели котла. 0...65535 - код ошибки + ecR_CodeErrorExt = 0x001F, // 0x001F R u16 Код ошибки котла (дополнительный). Зависит от марки и модели котла. 0...65535 - код ошибки + ecR_TempOutside = 0x0020, // 0x0020 R s8 Температура уличного датчика котла (при егоналичии). -65…+100 температура в градусах С + ecR_MemberCode = 0x0021, // 0x0021 R u16 Код производителя котла. Зависит от марки и модели котла. 0...65535 - код производителя + ecR_ModelCode = 0x0022, // 0x0022 R u16 Код модели котла. Зависит от марки и модели котла. 0...65535 - код модели + ecR_FlagErrorOT = 0x0023 // 0x0023 R s8 Флаги ошибок (только для котлов с интерфейсом OpenTherm) + // 0: Необходимо обслуживание + // 1: Котел заблокирован + // 2: Низкое давление в отопительном контуре + // 3: Ошибка розжига + // 4: Низкое давление воздуха + // 5: Перегрев теплоносителя в контуре +}; + +////////////////////////////// WRITE ///////////// + +enum WriteDataEctoControl //: uitn16_t +{ + ecW_SetTypeConnect = 0x0030, // 0x0030 W u8 Тип внешних подключений (будет сохранено в постоянной памяти адаптера) + // 0 - адаптер подключен к котлу + // 1 - котел подключен к внешнему устройству (панель или перемычка) + + ecW_TSetCH = 0x0031, // 0x0031 W int16 Уставка теплоносителя (будет сохранено в постоянной памяти адаптера). + // Будет передана котлу при старте адаптера, пока главным устройством не + // были записаны регистры уставки температуры. + // 0...1000 - температура уставки в десятых долях градуса С (например, для + // установки 45С нужно записать число 450). Во многих котлах необходимо до + // подключения адаптера необходимо поднять температуру теплоносителя + // отопления (нажимая “+” на панели котла) для согласования диапазона + // температуры теплоносителя, в противном случае температура теплоносителя + // может не достигнуть требуемого значения. + + ecW_TSetCHFaultConn = 0x0032, // 0x0032 W int16 Уставка теплоносителя в аварийном режиме(будет сохранено в постоянной памяти адаптера). + // Будет передана котлу в случае + // отсутствия связи с главным управляющим устройством. + // 0...1000 - температура уставки в десятых долях град. С (например, + // для установки 45С нужно записать число 450) + + ecW_TSetMinCH = 0x0033, // 0x0033 W u8 Нижний предел уставки теплоносителя 0...100 - температура уставки в град. С + // Не все котлы поддерживают этот параметр. Как правило, этот + // предел не должен быть ниже аналогичного предела, + // установленного в настройках котла. + + ecW_TSetMaxCH = 0x0034, // 0x0034 W u8 Верхний предел уставки теплоносителя 0...100 - температура уставки в град. С + // Не все котлы поддерживают этот параметр. Как правило, этот + // предел не должен быть выше аналогичного предела, + // установленного в настройках котла. + + ecW_TSetMinDHW = 0x0035, // 0x0035 W u8 Нижний предел уставки ГВС 0...100 - температура уставки в град. С + // Не все котлы поддерживают этот параметр. Как правило, этот + // предел не должен быть ниже аналогичного предела, + // установленного в настройках котла. + + ecW_TSetMaxDHW = 0x0036, // 0x0036 W u8 Верхний предел уставки ГВС 0...100 - температура уставки в град. С + // Не все котлы поддерживают этот параметр. Как правило, этот + // предел не должен быть выше аналогичного предела, + // установленного в настройках котла. + + ecW_TSetDHW = 0x0037, // 0x0037 W u8 Уставка ГВС (EPROM) 0...100 - температура уставки в град. С + // Для большинства котлов эта уставка должна находиться ниже + // предела, установленного в меню самого котла, иначе она может быть + // проигнорирована котлом. Для некоторых котлов стоит устанавливать + // этот параметр равным верхнему пределу уставки ГВС. + + ecW_SetMaxModLevel = 0x0038, // 0x0038 W u8 Уставка максимальной модуляции горелки (будет сохранено в постоянной памяти адаптера) 0...100 - уровень модуляции в процентах. + // Данный параметр поддерживается не всеми котлами. Для + // электрических трехфазных котлов возможно задать только 3 + // уровня модуляции (в диапазоне 0…100%). + + ecW_SetStatusBoiler = 0x0039, // 0x0039 W bitfields бит 0 - режим контура отопления (будет сохранено в постоянной памяти адаптера) 0 - отключен 1 - включен + // бит 1 - режим контура ГВС 0 - отключен 1 - включен + // бит 2 - “второй контур”, используется только некоторыми котлами с + // интерфейсом OpenTherm и может отвечать за активацию бойлера + // косвенного нагрева или встроенной функции ГВС. 0 - отключен 1 - включен + + + ecW_Command = 0x0080 // 0x0080 W uint16 команда + // 0 - нет команды + // 1 - CH water filling (зарезервировано) + // 2 - перезагрузка адаптера + // 3 - сброс ошибок котла + // 4..65525 - зарезервировано + // При записи любой команды, кроме «нет команды», сразу же меняется + // состояние регистра «Ответ на команду» - становится 2 – идет + // обработка команжы + + +}; + +///////////////////////////////////////// Регистры состояния: + +/* +0x0040…0x006F R i16 состояние данных в регистре ($addr - 0x30) + -2 - ошибка чтения/записи в котел + -1 - регистр не поддерживается + 0- для регистра на чтение означает, что данные из котла прочитаны и + валидны. Для регистра на запись означает, что данные успешно приняты + котлом. + 1- не инициализирован. + если регистр R: чтение соответствующих данные из котла не + приводилось, + если регистр W: адаптеру не было задано значение для записи + соответствующего значения в котле +*/ + + +/* +0x0081 R int16 Ответ на команду + -32768..-6 - зарезервировано + -5 – ошибка выполнения команды + -4 – неподдерживаемая котлом команда + -3 – не поддерживаемый котлом идентификатор устройства + -2 – не поддерживается данный адаптером + -1 – не получен ответ за отведенное врекмя + 0 – команда выполнена успешно + 1 – не было команды (значение по умолчанию) + 2 – идет обработка команды (обмен данными) + 3..32767 - зарезервировано + Если команда после перезагрузки адаптера не давалась, регистр + читается как 1 - не было команды. +*/ diff --git a/src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp b/src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp new file mode 100644 index 00000000..18511cc6 --- /dev/null +++ b/src/modules/exec/EctoControlAdapter/EctoControlAdapter.cpp @@ -0,0 +1,814 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include +#include + +#include "ModbusEC.h" +#include "AdapterCommon.h" +// #include "Stream.h" +#include + +// class ModbusUart; +Stream *_modbusUART = nullptr; + +#define UART_LINE 2 +uint8_t _DIR_PIN = 0; +// Modbus stuff +// Данные Modbus по умолчанию + +#define MODBUS_RX_PIN 18 // Rx pin +#define MODBUS_TX_PIN 19 // Tx pin +#define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication + +void modbusPreTransmission() +{ + // delay(500); + if (_DIR_PIN) + digitalWrite(_DIR_PIN, HIGH); +} + +// Pin 4 made low for Modbus receive mode +// Контакт 4 установлен на низком уровне для режима приема Modbus +void modbusPostTransmission() +{ + if (_DIR_PIN) + digitalWrite(_DIR_PIN, LOW); + // delay(500); +} + +// ModbusMaster node; + +// RsEctoControl *rsEC; + +class EctoControlAdapter : public IoTItem +{ +private: + int _rx = MODBUS_RX_PIN; // адреса прочитаем с веба + int _tx = MODBUS_TX_PIN; + int _baud = MODBUS_SERIAL_BAUD; + String _prot = "SERIAL_8N1"; + int protocol = SERIAL_8N1; + uint8_t _addr = 0xF0; // Адрес слейва от 1 до 247 + uint8_t _type = 0x14; // Тип устройства: 0x14 – адаптер OpenTherm (вторая версия); 0x11 – адаптер OpenTherm (первая версия, снята с производства) + uint8_t _debugLevel; // Дебаг + + ModbusMaster node; + uint8_t _debug; + // Stream *_modbusUART; + + BoilerInfo info; + BoilerStatus status; + + uint16_t code; + uint16_t codeExt; + uint8_t flagErr; + float flow; + float maxSetCH; + float maxSetDHW; + float minSetCH; + float minSetDHW; + float modLevel; + float press; + float tCH; + float tDHW; + float tOut; + bool enableCH; + bool enableDHW; + bool enableCH2; + bool _isNetworkActive; + bool _mqttIsConnect; + +public: + EctoControlAdapter(String parameters) : IoTItem(parameters) + { + _DIR_PIN = 0; + _addr = jsonReadInt(parameters, "addr"); // адреса slave прочитаем с веба + _rx = jsonReadInt(parameters, "RX"); // прочитаем с веба + _tx = jsonReadInt(parameters, "TX"); + _DIR_PIN = jsonReadInt(parameters, "DIR_PIN"); + _baud = jsonReadInt(parameters, "baud"); + _prot = jsonReadStr(parameters, "protocol"); + jsonRead(parameters, "debug", _debugLevel); + + if (_prot == "SERIAL_8N1") + { + protocol = SERIAL_8N1; + } + else if (_prot == "SERIAL_8N2") + { + protocol = SERIAL_8N2; + } + + // Serial2.begin(baud-rate, protocol, RX pin, TX pin); + + _modbusUART = new HardwareSerial(UART_LINE); + + if (_debugLevel > 2) + { + SerialPrint("I", "EctoControlAdapter", "baud: " + String(_baud) + ", protocol: " + String(protocol, HEX) + ", RX: " + String(_rx) + ", TX: " + String(_tx)); + } + ((HardwareSerial *)_modbusUART)->begin(_baud, protocol, _rx, _tx); // выбираем тип протокола, скорость и все пины с веба + ((HardwareSerial *)_modbusUART)->setTimeout(200); + //_modbusUART = &serial; + node.begin(_addr, _modbusUART); + + node.preTransmission(modbusPreTransmission); + node.postTransmission(modbusPostTransmission); + + if (_DIR_PIN) + { + _DIR_PIN = _DIR_PIN; + pinMode(_DIR_PIN, OUTPUT); + digitalWrite(_DIR_PIN, LOW); + } + + // 0x14 – адаптер OpenTherm (вторая версия) + // 0x15 – адаптер eBus + // 0x16 – адаптер Navien + if (_addr > 0) + { + uint16_t type; + readFunctionModBus(0x0003, type); + type = type >> 8; + if (0x14 != type && 0x15 != type && 0x16 != type) + { + SerialPrint("E", "EctoControlAdapter", "Не подходящее устройство, type: " + String(type, HEX)); + } + getModelVersion(); + getBoilerInfo(); + getBoilerStatus(); + } + else if (_addr == 0) + { // если адреса нет, то шлем широковещательный запрос адреса + uint8_t addr = node.readAddresEctoControl(); + SerialPrint("I", "EctoControlAdapter", "readAddresEctoControl, addr: " + String(addr, HEX) + " - Enter to configuration"); + } + } + + void doByInterval() + { + if (_addr > 0) + { + // readBoilerInfo(); + getBoilerStatus(); + + getCodeError(); + getCodeErrorExt(); + if (info.adapterType == 0) + getFlagErrorOT(); + // getFlowRate(); + // getMaxSetCH(); + // getMaxSetDHW(); + // getMinSetCH(); + // getMinSetDHW(); + getModLevel(); + getPressure(); + getTempCH(); + getTempDHW(); + getTempOutside(); + } + } + + void loop() + { + // для новых версий IoTManager + IoTItem::loop(); + } + + IoTValue execute(String command, std::vector ¶m) + { + if (command == "getModelVersion") + { + getModelVersion(); + } + if (command == "getBoilerInfo") + { + getBoilerInfo(); + } + if (command == "getBoilerStatus") + { + getBoilerStatus(); + } + if (command == "getCodeError") + { + getCodeError(); + } + if (command == "getCodeErrorExt") + { + getCodeErrorExt(); + } + if (command == "getFlagErrorOT") + { + getFlagErrorOT(); + } + if (command == "getFlowRate") + { + getFlowRate(); + } + if (command == "getMaxSetCH") + { + getMaxSetCH(); + } + if (command == "getMaxSetDHW") + { + getMaxSetDHW(); + } + if (command == "getMinSetCH") + { + getMinSetCH(); + } + if (command == "getMinSetDHW") + { + getMinSetDHW(); + } + if (command == "getModLevel") + { + getModLevel(); + } + if (command == "getPressure") + { + getPressure(); + } + if (command == "getTempCH") + { + getTempCH(); + } + if (command == "getTempDHW") + { + getTempDHW(); + } + if (command == "getTempOutside") + { + getTempOutside(); + } + + if (command == "setTypeConnect") + { + setTypeConnect(param[0].valD); + } + if (command == "setTCH") + { + setTCH(param[0].valD); + } + if (command == "setTCHFaultConn") + { + setTCHFaultConn(param[0].valD); + } + if (command == "setMinCH") + { + setMinCH(param[0].valD); + } + if (command == "setMaxCH") + { + setMaxCH(param[0].valD); + } + if (command == "setMinDHW") + { + setMinDHW(param[0].valD); + } + if (command == "setMaxDHW") + { + setMaxDHW(param[0].valD); + } + if (command == "setTDHW") + { + setTDHW(param[0].valD); + } + if (command == "setMaxModLevel") + { + setMaxModLevel(param[0].valD); + } + if (command == "setStatusCH") + { + setStatusCH((bool)param[0].valD); + } + if (command == "setStatusDHW") + { + setStatusDHW((bool)param[0].valD); + } + if (command == "setStatusCH2") + { + setStatusCH2((bool)param[0].valD); + } + + if (command == "lockOutReset") + { + lockOutReset(); + } + if (command == "rebootAdapter") + { + rebootAdapter(); + } + return {}; + } + + void publishData(String widget, String status) + { + + IoTItem *tmp = findIoTItem(widget); + if (tmp) + tmp->setValue(status, true); + else + { + if (_debugLevel > 0) + SerialPrint("i", "NEW", widget + " = " + status); + } + } + + static void sendTelegramm(String msg) + { + if (tlgrmItem) + tlgrmItem->sendTelegramMsg(false, msg); + } + + ~EctoControlAdapter(){}; + + bool writeFunctionModBus(const uint16_t ®, uint16_t &data) + { + // set word 0 of TX buffer to least-significant word of counter (bits 15..0) + // node.setTransmitBuffer(1, lowWord(data)); + // set word 1 of TX buffer to most-significant word of counter (bits 31..16) + node.setTransmitBuffer(0, data); + // slave: write TX buffer to (2) 16-bit registers starting at register 0 + uint8_t result = node.writeMultipleRegisters(reg, 1); + node.clearTransmitBuffer(); + if (_debug > 2) + { + SerialPrint("I", "EctoControlAdapter", "writeSingleRegister, addr: " + String((uint8_t)_addr, HEX) + ", reg: 0x" + String(reg, HEX) + ", state: " + String(data) + " = result: " + String(result, HEX)); + } + if (result == 0) + return true; + else + return false; + } + + bool readFunctionModBus(const uint16_t ®, uint16_t &reading) + { + if (_addr == 0) return false; + // float retValue = 0; + if (_modbusUART) + { + // if (!addr) + // addr = _addr; + node.begin(_addr, _modbusUART); + uint8_t result; + // uint32_t reading; + + if (reg == 0x0000) + result = node.readHoldingRegisters(reg, 4); + else + result = node.readHoldingRegisters(reg, 1); + if (_debug > 2) + SerialPrint("I", "EctoControlAdapter", "readHoldingRegisters, addr: " + String(_addr, HEX) + ", reg: 0x" + String(reg, HEX) + " = result: " + String(result, HEX)); + // break; + if (result == node.ku8MBSuccess) + { + if (reg == 0x0000) + { + reading = node.getResponseBuffer(0x03); + reading = (uint16_t)((uint8_t)(reading) >> 8); + SerialPrint("I", "EctoControlAdapter", "read info, addr: " + String(_addr, HEX) + ", type: " + String(reading, HEX)); + } + else + { + reading = node.getResponseBuffer(0x00); + if (_debug > 2) + SerialPrint("I", "EctoControlAdapter", "Success, Received data, register: " + String(reg) + " = " + String(reading, HEX)); + } + node.clearResponseBuffer(); + } + else + { + if (_debug > 2) + SerialPrint("E", "EctoControlAdapter", "Failed, Response Code: " + String(result, HEX)); + return false; + } + + if (reading != 0x7FFF) + return true; + else + return false; + } + return false; + } + + bool getModelVersion() + { + uint16_t reqData; + bool ret; + ret = readFunctionModBus(ReadDataEctoControl::ecR_MemberCode, info.boilerMemberCode); + ret = readFunctionModBus(ReadDataEctoControl::ecR_ModelCode, info.boilerModelCode); + ret = readFunctionModBus(ReadDataEctoControl::ecR_AdaperVersion, reqData); + info.adapterHardVer = highByte(reqData); + info.adapterSoftVer = lowByte(reqData); + return ret; + } + + bool getBoilerInfo() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_AdapterInfo, reqData); + info.adapterType = highByte(reqData) & 0xF8; + info.boilerStatus = (highByte(reqData) >> 3) & 1u; + info.rebootStatus = lowByte(reqData); + if (ret) + { + publishData("adapterType", String(info.adapterType)); + publishData("boilerStatus", String(info.boilerStatus)); + publishData("rebootStatus", String(info.rebootStatus)); + } + return ret; + } + bool getBoilerStatus() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_BoilerStatus, reqData); + status.burnStatus = (lowByte(reqData) >> 0) & 1u; + status.CHStatus = (lowByte(reqData) >> 1) & 1u; + status.DHWStatus = (lowByte(reqData) >> 2) & 1u; + if (ret) + { + publishData("burnStatus", String(status.burnStatus)); + publishData("CHStatus", String(status.CHStatus)); + publishData("DHWStatus", String(status.DHWStatus)); + } + return ret; + } + bool getCodeError() + { + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_CodeError, code); + if (ret) + { + publishData("codeError", String(code)); + if (code) + sendTelegramm("EctoControlAdapter: код ошибки: " + String((int)code)); + } + return ret; + } + bool getCodeErrorExt() + { + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_CodeErrorExt, codeExt); + if (ret) + { + publishData("codeErrorExt", String(codeExt)); + if (codeExt) + sendTelegramm("EctoControlAdapter: код ошибки: " + String((int)codeExt)); + } + return ret; + } + bool getFlagErrorOT() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_FlagErrorOT, reqData); + flagErr = lowByte(reqData); + if (ret) + { + publishData("flagErr", String(flagErr)); + switch (flagErr) + { + case 0: + sendTelegramm("EctoControlAdapter: Необходимо обслуживание!"); + break; + case 1: + sendTelegramm("EctoControlAdapter: Котёл заблокирован!"); + break; + case 2: + sendTelegramm("EctoControlAdapter: Низкое давление в отопительном контуре!"); + break; + case 3: + sendTelegramm("EctoControlAdapter: Ошибка розжига!"); + break; + case 4: + sendTelegramm("EctoControlAdapter: Низкое давления воздуха!"); + break; + case 5: + sendTelegramm("EctoControlAdapter: Перегрев теплоносителя в контуре!"); + break; + default: + break; + } + } + return ret; + } + + bool getFlowRate() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_FlowRate, reqData); + flow = lowByte(reqData) / 10.f; + if (ret) + publishData("flowRate", String(flow)); + return ret; + } + bool getMaxSetCH() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MaxSetCH, reqData); + maxSetCH = (float)lowByte(reqData); + if (ret) + publishData("maxSetCH", String(maxSetCH)); + return ret; + } + bool getMaxSetDHW() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MaxSetDHW, reqData); + maxSetDHW = (float)lowByte(reqData); + if (ret) + publishData("maxSetDHW", String(maxSetDHW)); + return ret; + } + bool getMinSetCH() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MinSetCH, reqData); + minSetCH = (float)lowByte(reqData); + if (ret) + publishData("minSetCH", String(minSetCH)); + return ret; + } + bool getMinSetDHW() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_MinSetDHW, reqData); + minSetDHW = (float)lowByte(reqData); + if (ret) + publishData("minSetDHW", String(minSetDHW)); + return ret; + } + bool getModLevel() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_ModLevel, reqData); + modLevel = (float)lowByte(reqData); + if (ret) + publishData("modLevel", String(modLevel)); + return ret; + } + bool getPressure() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_Pressure, reqData); + press = lowByte(reqData) / 10.f; + if (ret) + publishData("press", String(press)); + return ret; + } + bool getTempCH() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_TempCH, reqData); + tCH = reqData / 10.f; + if (ret) + publishData("tCH", String(tCH)); + return ret; + } + bool getTempDHW() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_TempDHW, reqData); + tDHW = reqData / 10.f; + if (ret) + publishData("tDHW", String(tDHW)); + return ret; + } + bool getTempOutside() + { + uint16_t reqData; + bool ret = readFunctionModBus(ReadDataEctoControl::ecR_TempOutside, reqData); + tOut = (float)lowByte(reqData); + if (ret) + publishData("tOut", String(tOut)); + return ret; + } + + bool setTypeConnect(float &data) + { + bool ret = false; + uint16_t data16 = data; + if (writeFunctionModBus(ecW_SetTypeConnect, data16)) + { + // TODO запросить результат записи у адаптера + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setTypeConnect"); + } + return ret; + } + bool setTCH(float &data) + { + bool ret = false; + uint16_t d16 = data * 10; + if (writeFunctionModBus(ecW_TSetCH, d16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setTCH"); + } + + return ret; + } + bool setTCHFaultConn(float &data) + { + bool ret = false; + uint16_t d16 = data * 10; + if (writeFunctionModBus(ecW_TSetCHFaultConn, d16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setTCHFaultConn"); + } + return ret; + } + bool setMinCH(float &data) + { + bool ret = false; + uint16_t data16 = data; + if (writeFunctionModBus(ecW_TSetMinCH, data16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setMinCH"); + } + return ret; + } + bool setMaxCH(float &data) + { + bool ret = false; + uint16_t data16 = data; + if (writeFunctionModBus(ecW_TSetMaxCH, data16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setMaxCH"); + } + return ret; + } + bool setMinDHW(float &data) + { + bool ret = false; + uint16_t data16 = data; + if (writeFunctionModBus(ecW_TSetMinDHW, data16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setMinDHW"); + } + return ret; + } + bool setMaxDHW(float &data) + { + bool ret = false; + uint16_t data16 = data; + if (writeFunctionModBus(ecW_TSetMaxDHW, data16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setMaxDHW"); + } + return ret; + } + bool setTDHW(float &data) + { + bool ret = false; + uint16_t data16 = data; + if (writeFunctionModBus(ecW_TSetDHW, data16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setTDHW"); + } + return ret; + } + bool setMaxModLevel(float &data) + { + bool ret = false; + uint16_t data16 = data; + if (writeFunctionModBus(ecW_SetMaxModLevel, data16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setMaxModLevel"); + } + return ret; + } + + bool setStatusCH(bool data) + { + bool ret = false; + enableCH = data; + uint16_t stat = enableCH | (enableDHW << 1) | (enableCH2 << 2); + if (writeFunctionModBus(ecW_SetStatusBoiler, stat)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setStatusCH"); + } + return ret; + } + bool setStatusDHW(bool data) + { + bool ret = false; + enableDHW = data; + uint16_t stat = enableCH | (enableDHW << 1) | (enableCH2 << 2); + if (writeFunctionModBus(ecW_SetStatusBoiler, stat)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setStatusDHW"); + } + return ret; + } + bool setStatusCH2(bool data) + { + bool ret = false; + enableCH2 = data; + uint16_t stat = enableCH | (enableDHW << 1) | (enableCH2 << 2); + if (writeFunctionModBus(ecW_SetStatusBoiler, stat)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, setStatusCH2"); + } + return ret; + } + + bool lockOutReset() + { + bool ret = false; + uint16_t d16 = comm_LockOutReset; + if (writeFunctionModBus(ecW_Command, d16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, lockOutReset"); + } + return ret; + } + bool rebootAdapter() + { + bool ret = false; + uint16_t d16 = comm_RebootAdapter; + if (writeFunctionModBus(ecW_Command, d16)) + { + ret = true; + } + else + { + if (_debug > 1) + SerialPrint("E", "EctoControlAdapter", "Failed, rebootAdapter"); + } + return ret; + } +}; + +void *getAPI_EctoControlAdapter(String subtype, String param) +{ + + if (subtype == F("ecAdapter")) + { + return new EctoControlAdapter(param); + } + { + return nullptr; + } +} diff --git a/src/modules/exec/EctoControlAdapter/ModbusEC.cpp b/src/modules/exec/EctoControlAdapter/ModbusEC.cpp new file mode 100644 index 00000000..d9d0818e --- /dev/null +++ b/src/modules/exec/EctoControlAdapter/ModbusEC.cpp @@ -0,0 +1,583 @@ + +#include "ModbusEC.h" + +#define COUNT_BIT_AVAIL 5 +#define COUNT_BIT_AVAIL_46F 4 + +ModbusMaster::ModbusMaster(void) +{ + _idle = 0; + _preTransmission = 0; + _postTransmission = 0; +} + +/** +Initialize class object. + +Assigns the Modbus slave ID and serial port. +Call once class has been instantiated, typically within setup(). + +@param slave Modbus slave ID (1..255) +@param &serial reference to serial port object (Serial, Serial1, ... Serial3) +@ingroup setup +*/ +void ModbusMaster::begin(uint8_t slave, Stream *serial) +{ + // txBuffer = (uint16_t*) calloc(ku8MaxBufferSize, sizeof(uint16_t)); + _u8MBSlave = slave; + _serial = serial; + _u8TransmitBufferIndex = 0; + u16TransmitBufferLength = 0; + +#if __MODBUSMASTER_DEBUG__ + pinMode(__MODBUSMASTER_DEBUG_PIN_A__, OUTPUT); + pinMode(__MODBUSMASTER_DEBUG_PIN_B__, OUTPUT); +#endif +} + +void ModbusMaster::beginTransmission(uint16_t u16Address) +{ + _u16WriteAddress = u16Address; + _u8TransmitBufferIndex = 0; + u16TransmitBufferLength = 0; +} + +// eliminate this function in favor of using existing MB request functions +uint8_t ModbusMaster::requestFrom(uint16_t address, uint16_t quantity) +{ + uint8_t read; + // clamp to buffer length + if (quantity > ku8MaxBufferSize) + { + quantity = ku8MaxBufferSize; + } + // set rx buffer iterator vars + _u8ResponseBufferIndex = 0; + _u8ResponseBufferLength = read; + + return read; +} + +void ModbusMaster::sendBit(bool data) +{ + uint8_t txBitIndex = u16TransmitBufferLength % 16; + if ((u16TransmitBufferLength >> 4) < ku8MaxBufferSize) + { + if (0 == txBitIndex) + { + _u16TransmitBuffer[_u8TransmitBufferIndex] = 0; + } + bitWrite(_u16TransmitBuffer[_u8TransmitBufferIndex], txBitIndex, data); + u16TransmitBufferLength++; + _u8TransmitBufferIndex = u16TransmitBufferLength >> 4; + } +} + +void ModbusMaster::send(uint16_t data) +{ + if (_u8TransmitBufferIndex < ku8MaxBufferSize) + { + _u16TransmitBuffer[_u8TransmitBufferIndex++] = data; + u16TransmitBufferLength = _u8TransmitBufferIndex << 4; + } +} + +void ModbusMaster::send(uint32_t data) +{ + send(lowWord(data)); + send(highWord(data)); +} + +void ModbusMaster::send(uint8_t data) +{ + send(word(data)); +} + +uint8_t ModbusMaster::available(void) +{ + return _u8ResponseBufferLength - _u8ResponseBufferIndex; +} + +uint16_t ModbusMaster::receive(void) +{ + if (_u8ResponseBufferIndex < _u8ResponseBufferLength) + { + return _u16ResponseBuffer[_u8ResponseBufferIndex++]; + } + else + { + return 0xFFFF; + } +} + +/** +Set idle time callback function (cooperative multitasking). + +This function gets called in the idle time between transmission of data +and response from slave. Do not call functions that read from the serial +buffer that is used by ModbusMaster. Use of i2c/TWI, 1-Wire, other +serial ports, etc. is permitted within callback function. + +@see ModbusMaster::ModbusMasterTransaction() +*/ +void ModbusMaster::idle(void (*idle)()) +{ + _idle = idle; +} + +/** +Set pre-transmission callback function. + +This function gets called just before a Modbus message is sent over serial. +Typical usage of this callback is to enable an RS485 transceiver's +Driver Enable pin, and optionally disable its Receiver Enable pin. + +@see ModbusMaster::ModbusMasterTransaction() +@see ModbusMaster::postTransmission() +*/ +void ModbusMaster::preTransmission(void (*preTransmission)()) +{ + _preTransmission = preTransmission; +} + +/** +Set post-transmission callback function. + +This function gets called after a Modbus message has finished sending +(i.e. after all data has been physically transmitted onto the serial +bus). + +Typical usage of this callback is to enable an RS485 transceiver's +Receiver Enable pin, and disable its Driver Enable pin. + +@see ModbusMaster::ModbusMasterTransaction() +@see ModbusMaster::preTransmission() +*/ +void ModbusMaster::postTransmission(void (*postTransmission)()) +{ + _postTransmission = postTransmission; +} + +/** +Retrieve data from response buffer. + +@see ModbusMaster::clearResponseBuffer() +@param u8Index index of response buffer array (0x00..0x3F) +@return value in position u8Index of response buffer (0x0000..0xFFFF) +@ingroup buffer +*/ +uint16_t ModbusMaster::getResponseBuffer(uint8_t u8Index) +{ + if (u8Index < ku8MaxBufferSize) + { + return _u16ResponseBuffer[u8Index]; + } + else + { + return 0xFFFF; + } +} + +/** +Clear Modbus response buffer. + +@see ModbusMaster::getResponseBuffer(uint8_t u8Index) +@ingroup buffer +*/ +void ModbusMaster::clearResponseBuffer() +{ + uint8_t i; + + for (i = 0; i < ku8MaxBufferSize; i++) + { + _u16ResponseBuffer[i] = 0; + } +} + +/** +Place data in transmit buffer. + +@see ModbusMaster::clearTransmitBuffer() +@param u8Index index of transmit buffer array (0x00..0x3F) +@param u16Value value to place in position u8Index of transmit buffer (0x0000..0xFFFF) +@return 0 on success; exception number on failure +@ingroup buffer +*/ +uint8_t ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value) +{ + if (u8Index < ku8MaxBufferSize) + { + _u16TransmitBuffer[u8Index] = u16Value; + return ku8MBSuccess; + } + else + { + return ku8MBIllegalDataAddress; + } +} + +/** +Clear Modbus transmit buffer. + +@see ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value) +@ingroup buffer +*/ +void ModbusMaster::clearTransmitBuffer() +{ + uint8_t i; + + for (i = 0; i < ku8MaxBufferSize; i++) + { + _u16TransmitBuffer[i] = 0; + } +} + +/** +Modbus function 0x03 Read Holding Registers. + +This function code is used to read the contents of a contiguous block of +holding registers in a remote device. The request specifies the starting +register address and the number of registers. Registers are addressed +starting at zero. + +The register data in the response buffer is packed as one word per +register. + +@param u16ReadAddress address of the first holding register (0x0000..0xFFFF) +@param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device) +@return 0 on success; exception number on failure +@ingroup register +*/ +uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress, + uint16_t u16ReadQty) +{ + _u16ReadAddress = u16ReadAddress; + _u16ReadQty = u16ReadQty; + return ModbusMasterTransaction(ku8MBReadHoldingRegisters); +} + +/** +Modbus function 0x06 Write Single Register. + +This function code is used to write a single holding register in a +remote device. The request specifies the address of the register to be +written. Registers are addressed starting at zero. + +@param u16WriteAddress address of the holding register (0x0000..0xFFFF) +@param u16WriteValue value to be written to holding register (0x0000..0xFFFF) +@return 0 on success; exception number on failure +@ingroup register +*/ +uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, + uint16_t u16WriteValue) +{ + _u16WriteAddress = u16WriteAddress; + _u16WriteQty = 0; + _u16TransmitBuffer[0] = u16WriteValue; + return ModbusMasterTransaction(ku8MBWriteSingleRegister); +} + +/** +Modbus function 0x10 Write Multiple Registers. + +This function code is used to write a block of contiguous registers (1 +to 123 registers) in a remote device. + +The requested written values are specified in the transmit buffer. Data +is packed as one word per register. + +@param u16WriteAddress address of the holding register (0x0000..0xFFFF) +@param u16WriteQty quantity of holding registers to write (1..123, enforced by remote device) +@return 0 on success; exception number on failure +@ingroup register +*/ +uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress, + uint16_t u16WriteQty) +{ + _u16WriteAddress = u16WriteAddress; + _u16WriteQty = u16WriteQty; + return ModbusMasterTransaction(ku8MBWriteMultipleRegisters); +} + +// new version based on Wire.h +uint8_t ModbusMaster::writeMultipleRegisters() +{ + _u16WriteQty = _u8TransmitBufferIndex; + return ModbusMasterTransaction(ku8MBWriteMultipleRegisters); +} + +uint8_t ModbusMaster::readAddresEctoControl() +{ + _u16ReadAddress = 0x00; + _u16ReadQty = 1; + ModbusMasterTransaction(ku8MBProgRead46); + return getResponseBuffer(0x00); +} +uint8_t ModbusMaster::writeAddresEctoControl(uint8_t addr) +{ + _u16WriteAddress = 0x00; + _u16WriteQty = 1; + _u16TransmitBuffer[0] = (uint16_t)addr; + return ModbusMasterTransaction(ku8MBProgWrite47); +} + +/* _____PRIVATE FUNCTIONS____________________________________________________ */ +/** +Modbus transaction engine. +Sequence: + - assemble Modbus Request Application Data Unit (ADU), + based on particular function called + - transmit request over selected serial port + - wait for/retrieve response + - evaluate/disassemble response + - return status (success/exception) + +@param u8MBFunction Modbus function (0x01..0xFF) +@return 0 on success; exception number on failure +*/ +uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) +{ + uint8_t u8ModbusADU[24]; + uint8_t u8ModbusADUSize = 0; + uint8_t i, u8Qty; + uint16_t u16CRC; + uint32_t u32StartTime; + uint8_t u8BytesLeft = 8; + uint8_t u8MBStatus = ku8MBSuccess; + + // assemble Modbus Request Application Data Unit + if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47) + { + u8ModbusADU[u8ModbusADUSize++] = 0x00; + u8ModbusADU[u8ModbusADUSize++] = u8MBFunction; + } + else + { + u8ModbusADU[u8ModbusADUSize++] = _u8MBSlave; + u8ModbusADU[u8ModbusADUSize++] = u8MBFunction; + } + + switch (u8MBFunction) + { + case ku8MBReadHoldingRegisters: + u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadAddress); + u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadAddress); + u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadQty); + u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadQty); + break; + } + + switch (u8MBFunction) + { + case ku8MBWriteMultipleRegisters: + u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteAddress); + u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteAddress); + break; + } + + switch (u8MBFunction) + { + case ku8MBWriteMultipleRegisters: + u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty); + u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty); + u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty << 1); + + for (i = 0; i < lowByte(_u16WriteQty); i++) + { + u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i]); + u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i]); + } + break; + } + + // append CRC + u16CRC = 0xFFFF; + for (i = 0; i < u8ModbusADUSize; i++) + { + u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); + } + // if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47) + // { + // u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC); + // u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC); + // } + // else + // { + u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC); + u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC); + // } + + u8ModbusADU[u8ModbusADUSize] = 0; + + // flush receive buffer before transmitting request + while (_serial->read() != -1) + ; + + // transmit request + if (_preTransmission) + { + _preTransmission(); + } + for (i = 0; i < u8ModbusADUSize; i++) + { + _serial->write(u8ModbusADU[i]); + } + + u8ModbusADUSize = 0; + _serial->flush(); // flush transmit buffer + if (_postTransmission) + { + _postTransmission(); + } + + // loop until we run out of time or bytes, or an error occurs + u32StartTime = millis(); + while (u8BytesLeft && !u8MBStatus) + { + if (_serial->available()) + { +#if __MODBUSMASTER_DEBUG__ + digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, true); +#endif + uint8_t req = _serial->read(); + u8ModbusADU[u8ModbusADUSize++] = req; + Serial.print(req, HEX); + u8BytesLeft--; +#if __MODBUSMASTER_DEBUG__ + digitalWrite(__MODBUSMASTER_DEBUG_PIN_A__, false); +#endif + } + else + { +#if __MODBUSMASTER_DEBUG__ + digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, true); +#endif + if (_idle) + { + _idle(); + } +#if __MODBUSMASTER_DEBUG__ + digitalWrite(__MODBUSMASTER_DEBUG_PIN_B__, false); +#endif + } + + // evaluate slave ID, function code once enough bytes have been read + uint8_t count; + if (u8MBFunction == ku8MBProgRead46 || u8MBFunction == ku8MBProgWrite47) + count = COUNT_BIT_AVAIL_46F; + else + count = COUNT_BIT_AVAIL; + + if (u8ModbusADUSize == count) + { + if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47) + { + // verify response is for correct Modbus slave + if (u8ModbusADU[0] != _u8MBSlave) + { + // Serial.print(u8ModbusADU[0], HEX); + // Serial.print(" != "); + // Serial.println(_u8MBSlave, HEX); + + u8MBStatus = ku8MBInvalidSlaveID; + break; + } + + // verify response is for correct Modbus function code (mask exception bit 7) + if ((u8ModbusADU[1] & 0x7F) != u8MBFunction) + { + u8MBStatus = ku8MBInvalidFunction; + break; + } + } + // check whether Modbus exception occurred; return Modbus Exception Code + if (bitRead(u8ModbusADU[1], 7)) + { + u8MBStatus = u8ModbusADU[2]; + break; + } + + // evaluate returned Modbus function code + switch (u8ModbusADU[1]) + { + case ku8MBReadHoldingRegisters: + u8BytesLeft = u8ModbusADU[2]; + break; + + case ku8MBWriteMultipleRegisters: + u8BytesLeft = 3; + break; + + case ku8MBProgRead46: + u8BytesLeft = 1; + break; + + case ku8MBProgWrite47: + u8BytesLeft = 1; + break; + + default: + } + } + if ((millis() - u32StartTime) > ku16MBResponseTimeout) + { + u8MBStatus = ku8MBResponseTimedOut; + } + } + + if (u8MBFunction != ku8MBProgRead46 && u8MBFunction != ku8MBProgWrite47) + { + // verify response is large enough to inspect further + if (!u8MBStatus && u8ModbusADUSize >= COUNT_BIT_AVAIL) + { + // calculate CRC + u16CRC = 0xFFFF; + for (i = 0; i < (u8ModbusADUSize - 2); i++) + { + u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); + } + + // verify CRC + if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] || + highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])) + { + u8MBStatus = ku8MBInvalidCRC; + } + } + } + // disassemble ADU into words + if (!u8MBStatus) + { + // evaluate returned Modbus function code + switch (u8ModbusADU[1]) + { + case ku8MBReadHoldingRegisters: + // load bytes into word; response bytes are ordered H, L, H, L, ... + for (i = 0; i < (u8ModbusADU[2] >> 1); i++) + { + if (i < ku8MaxBufferSize) + { + _u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 3], u8ModbusADU[2 * i + 4]); + } + + _u8ResponseBufferLength = i; + } + break; + case ku8MBProgRead46: + Serial.print("ku8MBProgRead46"); + for (i = 0; i < (u8ModbusADUSize); i++) + { + Serial.println(u8ModbusADU[i], HEX); + } + + _u16ResponseBuffer[0] = (uint16_t)u8ModbusADU[2]; + _u8ResponseBufferLength = 1; + break; + } + } + + _u8TransmitBufferIndex = 0; + u16TransmitBufferLength = 0; + _u8ResponseBufferIndex = 0; + return u8MBStatus; +} diff --git a/src/modules/exec/EctoControlAdapter/ModbusEC.h b/src/modules/exec/EctoControlAdapter/ModbusEC.h new file mode 100644 index 00000000..3ddcea57 --- /dev/null +++ b/src/modules/exec/EctoControlAdapter/ModbusEC.h @@ -0,0 +1,97 @@ + +#ifndef ModbusEC_h +#define ModbusEC_h + +#define __MODBUSMASTER_DEBUG__ (0) +#define __MODBUSMASTER_DEBUG_PIN_A__ 4 +#define __MODBUSMASTER_DEBUG_PIN_B__ 5 + +#include "Arduino.h" +#include "util/crc16.h" +#include "util/word.h" + +/* _____CLASS DEFINITIONS____________________________________________________ */ +/** +Arduino class library for communicating with Modbus slaves over +RS232/485 (via RTU protocol). +*/ +class ModbusMaster +{ +public: + ModbusMaster(); + + void begin(uint8_t, Stream *serial); + void idle(void (*)()); + void preTransmission(void (*)()); + void postTransmission(void (*)()); + + static const uint8_t ku8MBIllegalFunction = 0x01; + static const uint8_t ku8MBIllegalDataAddress = 0x02; + static const uint8_t ku8MBIllegalDataValue = 0x03; + static const uint8_t ku8MBSlaveDeviceFailure = 0x04; + static const uint8_t ku8MBSuccess = 0x00; + static const uint8_t ku8MBInvalidSlaveID = 0xE0; + static const uint8_t ku8MBInvalidFunction = 0xE1; + static const uint8_t ku8MBResponseTimedOut = 0xE2; + static const uint8_t ku8MBInvalidCRC = 0xE3; + + uint16_t getResponseBuffer(uint8_t); + void clearResponseBuffer(); + uint8_t setTransmitBuffer(uint8_t, uint16_t); + void clearTransmitBuffer(); + + void beginTransmission(uint16_t); + uint8_t requestFrom(uint16_t, uint16_t); + void sendBit(bool); + void send(uint8_t); + void send(uint16_t); + void send(uint32_t); + uint8_t available(void); + uint16_t receive(void); + + uint8_t readHoldingRegisters(uint16_t, uint16_t); + uint8_t writeMultipleRegisters(uint16_t, uint16_t); + uint8_t writeMultipleRegisters(); + uint8_t writeSingleRegister(uint16_t, uint16_t); + uint8_t readAddresEctoControl(); + uint8_t writeAddresEctoControl(uint8_t); + +private: + Stream *_serial; ///< reference to serial port object + uint8_t _u8MBSlave; ///< Modbus slave (1..255) initialized in begin() + static const uint8_t ku8MaxBufferSize = 20; ///< size of response/transmit buffers + uint16_t _u16ReadAddress; ///< slave register from which to read + uint16_t _u16ReadQty; ///< quantity of words to read + uint16_t _u16ResponseBuffer[ku8MaxBufferSize]; ///< buffer to store Modbus slave response; read via GetResponseBuffer() + uint16_t _u16WriteAddress; ///< slave register to which to write + uint16_t _u16WriteQty; ///< quantity of words to write + uint16_t _u16TransmitBuffer[ku8MaxBufferSize]; ///< buffer containing data to transmit to Modbus slave; set via SetTransmitBuffer() + uint16_t *txBuffer; // from Wire.h -- need to clean this up Rx + uint8_t _u8TransmitBufferIndex; + uint16_t u16TransmitBufferLength; + uint16_t *rxBuffer; // from Wire.h -- need to clean this up Rx + uint8_t _u8ResponseBufferIndex; + uint8_t _u8ResponseBufferLength; + + // Modbus function codes for 16 bit access + static const uint8_t ku8MBReadHoldingRegisters = 0x03; ///< Modbus function 0x03 Read Holding Registers + static const uint8_t ku8MBWriteMultipleRegisters = 0x10; ///< Modbus function 0x10 Write Multiple Registers + static const uint8_t ku8MBWriteSingleRegister = 0x06; ///< Modbus function 0x06 Write Single Register + static const uint8_t ku8MBProgRead46 = 0x46; ///< EctoControl function 0x46 Устройство возвращает в ответе свой текущий адрес ADDR + static const uint8_t ku8MBProgWrite47 = 0x47; ///< EctoControl function 0x47 высылается ведущим устройством ведомому с указанием сменить свой имеющийся адрес на заданный + // высылается ведущим устройством единственному устройству на шине с неизвестным адресом + + // Modbus timeout [milliseconds] + static const uint16_t ku16MBResponseTimeout = 500; ///< Modbus timeout [milliseconds] + + // master function that conducts Modbus transactions + uint8_t ModbusMasterTransaction(uint8_t u8MBFunction); + + // idle callback function; gets called during idle time between TX and RX + void (*_idle)(); + // preTransmission callback function; gets called before writing a Modbus message + void (*_preTransmission)(); + // postTransmission callback function; gets called after a Modbus message has been sent + void (*_postTransmission)(); +}; +#endif diff --git a/src/modules/exec/EctoControlAdapter/modinfo.json b/src/modules/exec/EctoControlAdapter/modinfo.json new file mode 100644 index 00000000..d02ab116 --- /dev/null +++ b/src/modules/exec/EctoControlAdapter/modinfo.json @@ -0,0 +1,208 @@ +{ + "menuSection": "executive_devices", + "configItem": [ + { + "global": 0, + "name": "ectoCtrlAdapter", + "type": "Reading", + "subtype": "ecAdapter", + "id": "ecto", + "widget": "anydataTmp", + "page": "Котёл", + "descr": "Адаптер", + "int": 60, + "addr": 240, + "RX": 18, + "TX": 19, + "DIR_PIN": 4, + "baud": 19200, + "protocol": "SERIAL_8N1", + "debug": 1 + } + ], + "about": { + "authorName": "Mikhail Bubnov", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "", + "moduleName": "EctoControlAdapter", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "subTypes": [ + "ecAdapter" + ], + "title": "EctoControlAdapter", + "moduleDesc": "Управление отопительным котлом через адаптер EctoControl по протоколам OpenTherm, eBUS, Navien. Посредством Modbus RTU. Разъем 4P4C: 1-Желтый(красный)+12V; 2-Белый-GND; 3-Зелёный-A; 4-Коричневый(Синий)-B", + "propInfo": { + "addr": "Адрес slave, что бы узнать адрес - в конфиге адрес 0 и смотреть лог (требуется проверка)", + "int": "Количество секунд между опросами датчика.", + "RX": "Пин RX", + "TX": "Пин TX", + "DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен", + "baud": "скорость Uart", + "protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2", + "debug": "0 - отключить дебаг, 1 - включить вывод дебага, 2 - лог комманд, 3 - вывод modbus" + }, + "funcInfo": [ + { + "name": "getModelVersion", + "descr": "Запрос модели и версии адаптера и бойлера", + "params": [] + }, + { + "name": "getBoilerInfo", + "descr": "Запрос состояния связи с котлом, типа адаптера и код перезагрузки адаптера", + "params": [] + }, + { + "name": "getBoilerStatus", + "descr": "Запрос состояния контуров котла и горелки", + "params": [] + }, + { + "name": "getCodeError", + "descr": "Код ошибки котла (основной). Зависит от марки и модели котла.", + "params": [] + }, + { + "name": "getCodeErrorExt", + "descr": "Код ошибки котла (дополнительный). Зависит от марки и модели котла.", + "params": [] + }, + { + "name": "getFlagErrorOT", + "descr": "Стандартные флаги ошибок котла (только для котлов с интерфейсом OpenTherm)", + "params": [] + }, + { + "name": "getFlowRate", + "descr": "Текущий расхода ГВС", + "params": [] + }, + { + "name": "getMaxSetCH", + "descr": "Верхний предел уставки теплоносителя", + "params": [] + }, + { + "name": "getMaxSetDHW", + "descr": "Верхний предел уставки ГВС", + "params": [] + }, + { + "name": "getMinSetCH", + "descr": "Нижний предел уставки теплоносителя", + "params": [] + }, + { + "name": "getMinSetDHW", + "descr": "Нижний предел уставки ГВС", + "params": [] + }, + { + "name": "getModLevel", + "descr": "Текущая модуляция горелки", + "params": [] + }, + { + "name": "getPressure", + "descr": "Текущее Давление в контуре", + "params": [] + }, + { + "name": "getTempCH", + "descr": "Текущая температура теплоносителя", + "params": [] + }, + { + "name": "getTempDHW", + "descr": "Текущая температура ГВС", + "params": [] + }, + { + "name": "getTempOutside", + "descr": "Температура уличного датчика котла", + "params": [] + }, + { + "name": "setTypeConnect", + "descr": "Установить тип внешних подключений (сохраняется в EPROM Адаптера): 0 - адаптер подключен к котлу, 1 - котел подключен к внешнему устройству (панель или перемычка)", + "params": ["Тип подключения"] + }, + { + "name": "setTCH", + "descr": "Уставка температуры теплоносителя (сохраняется в EPROM Адаптера)", + "params": ["температура передаётся до десятых градуса"] + }, + { + "name": "setTDHW", + "descr": "Уставка температуры ГВС (сохраняется в EPROM Адаптера)", + "params": ["температура передаётся до десятых градуса"] + }, + { + "name": "setTCHFaultConn", + "descr": "Уставка теплоносителя в аварийном режиме (сохраняется в EPROM Адаптера). Будет передана котлу в случае отсутствия связи адаптера с управляющим устройством", + "params": ["температура передаётся до десятых градуса"] + }, + { + "name": "setMinCH", + "descr": "Задать нижний предел уставки теплоносителя", + "params": ["температура от 0 до 100"] + }, + { + "name": "setMaxCH", + "descr": "Задать верхний предел уставки теплоносителя", + "params": ["температура от 0 до 100"] + }, + { + "name": "setMinDHW", + "descr": "Задать нижний предел уставки ГВС", + "params": ["температура от 0 до 100"] + }, + { + "name": "setMaxDHW", + "descr": "Задать верхний предел уставки ГВС", + "params": ["температура от 0 до 100"] + }, + + { + "name": "setMaxModLevel", + "descr": "Уставка максимальной модуляции горелки (сохраняется в EPROM Адаптера)", + "params": ["уровень модуляции 0-100%"] + }, + { + "name": "setStatusCH", + "descr": "Установить режим (Включить) контура отопления; 0 - отключен, 1 - включен", + "params": ["вкл/откл отопления"] + }, + { + "name": "setStatusDHW", + "descr": "Установить режим (Включить) ГВС; 0 - отключен, 1 - включен", + "params": ["вкл/откл ГВС"] + }, + { + "name": "setStatusCH2", + "descr": "Установить режим (Включить) второго контура отопления; 0 - отключен, 1 - включен. используется только некоторыми котлами с интерфейсом OpenTherm и может отвечать за активацию бойлера косвенного нагрева или встроенной функции ГВС", + "params": ["вкл/откл второго контура отопления"] + }, + { + "name": "lockOutReset", + "descr": "Сброс ошибок котла", + "params": [] + }, + { + "name": "rebootAdapter", + "descr": "Перезагрузка адаптера", + "params": [] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp32c3m_4mb": ["exclude"] + } +} \ No newline at end of file diff --git a/src/modules/exec/EctoControlAdapter/util/crc16.h b/src/modules/exec/EctoControlAdapter/util/crc16.h new file mode 100644 index 00000000..27ea89ec --- /dev/null +++ b/src/modules/exec/EctoControlAdapter/util/crc16.h @@ -0,0 +1,88 @@ +/** +@file +CRC Computations + +@defgroup util_crc16 "util/crc16.h": CRC Computations +@code#include "util/crc16.h"@endcode + +This header file provides functions for calculating +cyclic redundancy checks (CRC) using common polynomials. +Modified by Doc Walker to be processor-independent (removed inline +assembler to allow it to compile on SAM3X8E processors). + +@par References: +Jack Crenshaw's "Implementing CRCs" article in the January 1992 issue of @e +Embedded @e Systems @e Programming. This may be difficult to find, but it +explains CRC's in very clear and concise terms. Well worth the effort to +obtain a copy. + +*/ +/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz + Copyright (c) 2005, 2007 Joerg Wunsch + Copyright (c) 2013 Dave Hylands + Copyright (c) 2013 Frederic Nadeau + Copyright (c) 2015 Doc Walker + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. */ + + +#ifndef _UTIL_CRC16_H_ +#define _UTIL_CRC16_H_ + + +/** @ingroup util_crc16 + Processor-independent CRC-16 calculation. + + Polynomial: x^16 + x^15 + x^2 + 1 (0xA001)
+ Initial value: 0xFFFF + + This CRC is normally used in disk-drive controllers. + + @param uint16_t crc (0x0000..0xFFFF) + @param uint8_t a (0x00..0xFF) + @return calculated CRC (0x0000..0xFFFF) +*/ +static uint16_t crc16_update(uint16_t crc, uint8_t a) +{ + int i; + + crc ^= a; + for (i = 0; i < 8; ++i) + { + if (crc & 1) + crc = (crc >> 1) ^ 0xA001; + else + crc = (crc >> 1); + } + + return crc; +} + + +#endif /* _UTIL_CRC16_H_ */ diff --git a/src/modules/exec/EctoControlAdapter/util/word.h b/src/modules/exec/EctoControlAdapter/util/word.h new file mode 100644 index 00000000..c72ad944 --- /dev/null +++ b/src/modules/exec/EctoControlAdapter/util/word.h @@ -0,0 +1,64 @@ +/** +@file +Utility Functions for Manipulating Words + +@defgroup util_word "util/word.h": Utility Functions for Manipulating Words +@code#include "util/word.h"@endcode + +This header file provides utility functions for manipulating words. + +*/ +/* + + word.h - Utility Functions for Manipulating Words + + This file is part of ModbusMaster. + + ModbusMaster is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ModbusMaster is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ModbusMaster. If not, see . + + Written by Doc Walker (Rx) + Copyright © 2009-2015 Doc Walker <4-20ma at wvfans dot net> + +*/ + + +#ifndef _UTIL_WORD_H_ +#define _UTIL_WORD_H_ + + +/** @ingroup util_word + Return low word of a 32-bit integer. + + @param uint32_t ww (0x00000000..0xFFFFFFFF) + @return low word of input (0x0000..0xFFFF) +*/ +static inline uint16_t lowWord(uint32_t ww) +{ + return (uint16_t) ((ww) & 0xFFFF); +} + + +/** @ingroup util_word + Return high word of a 32-bit integer. + + @param uint32_t ww (0x00000000..0xFFFFFFFF) + @return high word of input (0x0000..0xFFFF) +*/ +static inline uint16_t highWord(uint32_t ww) +{ + return (uint16_t) ((ww) >> 16); +} + + +#endif /* _UTIL_WORD_H_ */ diff --git a/src/modules/exec/IRremote/IRremote.cpp b/src/modules/exec/IRremote/IRremote.cpp new file mode 100644 index 00000000..cff41fd1 --- /dev/null +++ b/src/modules/exec/IRremote/IRremote.cpp @@ -0,0 +1,195 @@ +#include "Global.h" +#include "classes/IoTItem.h" + +#include +#include +#include +#include + +IRac* ac; + +const uint16_t kIrLed = 4; // The ESP GPIO pin to use that controls the IR LED. + +class IRremote : public IoTItem { + private: + + String _set_id; // заданная температура + + int enable = 1; + float _tmp; + + int _prot; // протокол + int _pinTx; // Выход модуля передатчика + + public: + IRremote(String parameters): IoTItem(parameters) { + jsonRead(parameters, "pinTx", _pinTx); //передатчик + jsonRead(parameters, "prot", _prot); // используемый протокол + jsonRead(parameters, "set_id", _set_id); // id установленной температуры + + if (_pinTx >= 0) { + IoTgpio.pinMode(_pinTx, OUTPUT); + IoTgpio.digitalWrite(_pinTx, false); } + + // Set up what we want to send. + // See state_t, opmode_t, fanspeed_t, swingv_t, & swingh_t in IRsend.h for + // all the various options. + + ac = new IRac(kIrLed); + ac->next.protocol = (decode_type_t)_prot; + ac->next.model = 1; // Некоторые кондиционеры имеют разные модели. Попробуйте только первое. + ac->next.mode = stdAc::opmode_t::kCool; // Сначала запустите в прохладном режиме. + ac->next.celsius = true; // Используйте градусы Цельсия в качестве единиц измерения температуры. Ложь = Фаренгейт + ac->next.degrees = 20; // 20 degrees. + ac->next.fanspeed = stdAc::fanspeed_t::kMedium; // Запустите вентилятор на средней скорости. + ac->next.swingv = stdAc::swingv_t::kOff; // Не поворачивайте вентилятор вверх или вниз. + ac->next.swingh = stdAc::swingh_t::kOff; // Не поворачивайте вентилятор влево или вправо. + ac->next.light = false; // Выключите все светодиоды/световые приборы/дисплеи, которые сможем. + ac->next.beep = false; // Если есть возможность, выключите все звуковые сигналы кондиционера. + ac->next.econo = false; // Turn off any economy modes if we can. + ac->next.filter = false; // Turn off any Ion/Mold/Health filters if we can. + ac->next.turbo = false; // Don't use any turbo/powerful/etc modes. + ac->next.quiet = false; // Don't use any quiet/silent/etc modes. + ac->next.sleep = -1; // Don't set any sleep time or modes. + ac->next.clean = false; // Turn off any Cleaning options if we can. + ac->next.clock = -1; // Don't set any current time if we can avoid it. + ac->next.power = false; // Initially start with the unit off. + + Serial.println("Try to turn on & off every supported A/C type ..."); +} + + + void doByInterval() {} + + IoTValue execute(String command, std::vector ¶m) { + + if (command == "on") { + + ac->next.power = true; // Типа команда включить + ac->sendAc(); // Send the message. + + SerialPrint("i", F("IRremote"), "Ballu AC on "); + } + + if (command == "off") { + + ac->next.power = false; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC off "); + } + + if (command == "cool") { + + ac->next.mode = stdAc::opmode_t::kCool; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC cool "); + } + + if (command == "heat") { + + ac->next.mode = stdAc::opmode_t::kHeat; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC heat "); + } + + if (command == "dry") { + + ac->next.mode = stdAc::opmode_t::kDry; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC dry "); + } + if (command == "auto") { + + ac->next.fanspeed = stdAc::fanspeed_t::kAuto; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC speed1 "); + } + if (command == "speedmin") { + + ac->next.fanspeed = stdAc::fanspeed_t::kMin; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC speed min "); + } + if (command == "speedlow") { + + ac->next.fanspeed = stdAc::fanspeed_t::kLow; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC speed low "); + } + if (command == "speedmed") { + + ac->next.fanspeed = stdAc::fanspeed_t::kMedium; // Надо выбрать под конкретный кондиционер из 6-ти вариантов + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC speed medium "); + } + if (command == "speedhigh") { + + ac->next.fanspeed = stdAc::fanspeed_t::kHigh; // Надо выбрать под конкретный кондиционер из 6-ти вариантов + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC speed high"); + } + if (command == "speedmax") { + + ac->next.fanspeed = stdAc::fanspeed_t::kMax; // Надо выбрать под конкретный кондиционер из 6-ти вариантов + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC speed max"); + } + + if (command == "speedmh") { + + ac->next.fanspeed = stdAc::fanspeed_t::kMediumHigh; + ac->sendAc(); + + SerialPrint("i", F("IRremote"), "Ballu AC speed max"); + } + + if (command == "setTemp") { + + // заданная температура + IoTItem *tmp = findIoTItem(_set_id); + if (tmp) + { + _tmp = ::atof(tmp->getValue().c_str()); + ac->next.degrees = _tmp; // set Temp 17 C - 30 C. + ac->sendAc(); // Send the message. + SerialPrint("i", F("IRremote"), "Ballu AC set temp -> " + String(_tmp) ); + } + else + { + // если не заполнены настройки кондиционера + setValue("ошибка настройки кондиционера"); + } + + } + + if (command == "swing") { + + ac->next.swingv = stdAc::swingv_t::kMiddle;; // Надо выбрать под конкретный кондиционер из 6-ти вариантов + ac->sendAc(); // Send the message. + + SerialPrint("i", F("IRremote"), "Ballu AC swing middle"); + } + + return {}; // команда поддерживает возвращаемое значения. Т.е. по итогу выполнения команды или общения с внешней системой, можно вернуть значение в сценарий для дальнейшей обработки + } + + ~IRremote() {}; +}; + +void* getAPI_IRremote(String subtype, String param) { + if (subtype == F("IRremote")) { + return new IRremote(param); + } else { + return nullptr; + } +} diff --git a/src/modules/exec/IRremote/modinfo.json b/src/modules/exec/IRremote/modinfo.json new file mode 100644 index 00000000..a19f6bd3 --- /dev/null +++ b/src/modules/exec/IRremote/modinfo.json @@ -0,0 +1,109 @@ +{ + "menuSection": "executive_devices", + + "configItem": [{ + + "global": 0, + "name": "Управление кондиционером", + "type": "Reading", + "subtype": "IRremote", + "id": "ir", + "pinTx": 4, + "set_id": "", + "int": 1, + "prot": 15 + }], + + "about": { + "authorName": "Serghei Crasnicov", + "authorContact": "https://t.me/Serghei63", + "authorGit": "https://github.com/Serghei63", + "specialThanks": "Mikhail Bubnov https://t.me/Mit4bmw , Олег Астахов https://t.me/Threedreality , Valentin Khandriga @Valiuhaaa", + "moduleName": "IRremote", + "moduleVersion": "2.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Эмулятор IR пульта", + "moduleDesc": "Позволяет управлять кондиционером и другой техникой по IR каналу", + "propInfo": { + "pinTx": "GPIO номер, к которому подключен инфракрасный приемник.", + "prot": " Цифровой код протокола . UNKNOWN = -1, UNUSED = 0, COOLIX - 15, SAMSUNG_AC - 46 ....", + "set_id": "id установленной температуры" + }, + "funcInfo": [ + { + "name": "on", + "descr": "Включить конциционер", + "params": [] + }, + { + "name": "off", + "descr": "Выключить конциционер", + "params": [] + }, + { + "name": "cool", + "descr": "Режим охлаждения", + "params": [] + }, + { + "name": "heat", + "descr": "Режим нагрева", + "params": [] + }, + { + "name": "dry", + "descr": "Режим осушения", + "params": [] + }, + { + "name": "speedmin", + "descr": "Минимальная скорость вентилятора", + "params": [] + }, + { + "name": "speedmed", + "descr": "Средняя скорость вентилятора", + "params": [] + }, + { + "name": "speedmh", + "descr": "Средне - высокая скорость вентилятора", + "params": [] + }, + { + "name": "speedhigh", + "descr": "Высокая скорость вентилятора", + "params": [] + }, + { + "name": "speedmax", + "descr": "Максимальная скорость вентилятора", + "params": [] + }, + { + "name": "setTemp", + "descr": "Установленная температура", + "params": [] + }, + { + "name": "swing", + "descr": "Управление шторкой вентилятора", + "params": [] + } + ] + }, + + "defActive": false, + + "usedLibs": { + "esp32*": [ + "https://github.com/crankyoldgit/IRremoteESP8266" + ], + "esp82*": [ + "https://github.com/crankyoldgit/IRremoteESP8266" + ] + } +} \ No newline at end of file diff --git a/src/modules/exec/IoTServo/modinfo.json b/src/modules/exec/IoTServo/modinfo.json index b5184c51..ec50f810 100644 --- a/src/modules/exec/IoTServo/modinfo.json +++ b/src/modules/exec/IoTServo/modinfo.json @@ -50,7 +50,7 @@ } ] }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "https://github.com/RoboticsBrno/ServoESP32#v1.0.3" diff --git a/src/modules/exec/Mcp23017/modinfo.json b/src/modules/exec/Mcp23017/modinfo.json index 5d2fba62..91a6f97c 100644 --- a/src/modules/exec/Mcp23017/modinfo.json +++ b/src/modules/exec/Mcp23017/modinfo.json @@ -34,7 +34,7 @@ "index": "Значения от 1 до 4, где при выборе 1 будет нумерация pin 100-115, при выборе 2 200-215 и т.д." } }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "adafruit/Adafruit MCP23017 Arduino Library@^2.1.0", diff --git a/src/modules/exec/MilightHub/MilightHub.cpp b/src/modules/exec/MilightHub/MilightHub.cpp new file mode 100644 index 00000000..f8eaa2f4 --- /dev/null +++ b/src/modules/exec/MilightHub/MilightHub.cpp @@ -0,0 +1,994 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "utils/SerialPrint.h" +#include "MqttClient.h" + +#include + +namespace { +const char* kTag = "MilightHub"; + +String escapeJson(const String& input) { + String escaped; + escaped.reserve(input.length() + 4); + + for (size_t i = 0; i < input.length(); ++i) { + const char c = input[i]; + switch (c) { + case '"': + escaped += F("\\\""); + break; + case '\\': + escaped += F("\\\\"); + break; + case '\b': + escaped += F("\\b"); + break; + case '\f': + escaped += F("\\f"); + break; + case '\n': + escaped += F("\\n"); + break; + case '\r': + escaped += F("\\r"); + break; + case '\t': + escaped += F("\\t"); + break; + default: { + const uint8_t code = static_cast(c); + if (code < 0x20) { + char buffer[7]; + snprintf(buffer, sizeof(buffer), "\\u%04X", code); + escaped += buffer; + } else { + escaped += c; + } + break; + } + } + } + + return escaped; +} + +bool isNumericString(const String& value) { + if (!value.length()) { + return false; + } + + bool seenDigit = false; + bool seenDot = false; + size_t idx = 0; + + if (value[0] == '-' || value[0] == '+') { + idx = 1; + } + + for (; idx < value.length(); ++idx) { + const char c = value[idx]; + if (c == '.') { + if (seenDot) { + return false; + } + seenDot = true; + continue; + } + if (!isDigit(c)) { + return false; + } + seenDigit = true; + } + + return seenDigit; +} + +String encodeUriComponent(const String& value) { + static const char* hex = "0123456789ABCDEF"; + String encoded; + encoded.reserve(value.length() * 3); + + for (size_t i = 0; i < value.length(); ++i) { + const uint8_t c = static_cast(value[i]); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || + c == '.' || c == '~') { + encoded += static_cast(c); + } else { + encoded += '%'; + encoded += hex[(c >> 4) & 0x0F]; + encoded += hex[c & 0x0F]; + } + } + + return encoded; +} + +String doubleToString(float value) { + if (isnan(value) || isinf(value)) { + return String(); + } + + const long whole = static_cast(value); + if (fabs(value - static_cast(whole)) < 0.0005f) { + return String(whole); + } + + return String(value, 3); +} + +String normalizeBoolString(String value) { + value.trim(); + value.toLowerCase(); + return value; +} + +String toParamString(const IoTValue& param) { + if (param.isDecimal) { + return doubleToString(param.valD); + } + + String copy = param.valS; + copy.trim(); + return copy; +} +} + +class MilightHubItem : public IoTItem { +public: + explicit MilightHubItem(String parameters); + + IoTValue execute(String command, std::vector& param) override; + void loop() override; + void onMqttRecive(String& topic, String& msg) override; + String getMqttExterSub() override; + +private: + struct RequestContext { + bool valid = false; + bool byAlias = true; + String alias; + String deviceId; + String remoteType; + int groupId = 0; + bool blockOnQueue = false; + bool normalized = false; + }; + +private: + RequestContext currentContext() const; + String buildTargetPath(const RequestContext& ctx) const; + String buildUrl(const String& path, bool block, bool normalized) const; + bool performRequest(const String& method, const RequestContext& ctx, const String& payload, String& response, int& statusCode); + void storeResponse(const String& payload, int statusCode, bool notify); + bool sendUpdate(const String& payload); + bool sendFieldUpdate(const String& field, const IoTValue& value); + bool sendCommandPayload(const String& payload); + bool sendCommandsPayload(const String& payload); + bool requestState(); + bool deleteState(); + bool postRawCommand(const String& payload, const String& remoteTypeOverride); + + String valueToJsonLiteral(const IoTValue& value) const; + String buildFieldPayload(const String& field, const IoTValue& value) const; + String buildCommandPayload(const String& command) const; + String buildCommandsPayload(const String& commands) const; + + void setAliasInternal(const String& alias); + void setDeviceInternal(const String& deviceId, const String& remoteType, int groupId); + void ensureMqttSubscription(); + void handleMqttMessage(String payload); + bool handleJsonMqttCommand(JsonVariantConst root); + void handlePlainMqttCommand(const String& payload); + IoTValue jsonVariantToIoTValue(JsonVariantConst variant) const; + IoTValue makeStringValue(const String& text) const; + void publishMqttResponse(const String& payload, int statusCode); + String resolveTopic(const String& topic, bool addPrefix) const; + +private: + String _host; + uint16_t _port = 80; + bool _useAlias = true; + String _alias; + String _deviceId; + String _remoteType; + int _groupId = 0; + bool _blockOnQueue = false; + bool _normalizedResponse = false; + bool _rememberResponse = true; + String _username; + String _password; + uint16_t _timeoutMs = 5000; + String _mqttCommandTopic; + bool _mqttCommandAddPrefix = false; + String _mqttResponseTopic; + bool _mqttResponseAddPrefix = false; + String _resolvedCommandTopic; + String _resolvedResponseTopic; + bool _mqttSubscribed = false; +}; + +MilightHubItem::MilightHubItem(String parameters) : IoTItem(parameters) { + _host = jsonReadStr(parameters, F("host")); + if (_host.length() == 0) { + SerialPrint("W", kTag, F("host not set")); + } + + const int port = jsonReadInt(parameters, F("port")); + if (port > 0 && port <= 65535) { + _port = static_cast(port); + } + + String targetType = jsonReadStr(parameters, F("targetType")); + targetType.toLowerCase(); + if (targetType == F("device")) { + _useAlias = false; + } + + _alias = jsonReadStr(parameters, F("alias")); + _deviceId = jsonReadStr(parameters, F("deviceId")); + _remoteType = jsonReadStr(parameters, F("remoteType")); + _groupId = jsonReadInt(parameters, F("groupId")); + if (_groupId < 0) { + _groupId = 0; + } + + _blockOnQueue = jsonReadBool(parameters, F("blockOnQueue")); + _normalizedResponse = jsonReadBool(parameters, F("normalizedResponse")); + + const String remember = jsonReadStr(parameters, F("storeResponse")); + if (remember.length()) { + String normalized = remember; + normalized.toLowerCase(); + _rememberResponse = (normalized == F("true")); + } else { + _rememberResponse = jsonReadBool(parameters, F("storeResponse")); + } + + _username = jsonReadStr(parameters, F("username")); + _password = jsonReadStr(parameters, F("password")); + + const int timeout = jsonReadInt(parameters, F("timeout")); + if (timeout > 0) { + _timeoutMs = static_cast(timeout); + } + + _mqttCommandTopic = jsonReadStr(parameters, F("mqttCommandTopic")); + _mqttCommandAddPrefix = true; + jsonRead(parameters, F("mqttCommandUsePrefix"), _mqttCommandAddPrefix, false); + _mqttResponseTopic = jsonReadStr(parameters, F("mqttResponseTopic")); + _mqttResponseAddPrefix = true; + jsonRead(parameters, F("mqttResponseUsePrefix"), _mqttResponseAddPrefix, false); + _resolvedCommandTopic = resolveTopic(_mqttCommandTopic, _mqttCommandAddPrefix); + _resolvedResponseTopic = resolveTopic(_mqttResponseTopic, _mqttResponseAddPrefix); + if (_resolvedCommandTopic.length() && mqttIsConnect()) { + mqttSubscribeExternal(_mqttCommandTopic, _mqttCommandAddPrefix); + _mqttSubscribed = true; + } + + value.isDecimal = false; +} + +void MilightHubItem::loop() { + ensureMqttSubscription(); + IoTItem::loop(); +} + +MilightHubItem::RequestContext MilightHubItem::currentContext() const { + RequestContext ctx; + ctx.blockOnQueue = _blockOnQueue; + ctx.normalized = _normalizedResponse; + + if (_useAlias) { + ctx.byAlias = true; + ctx.alias = _alias; + ctx.valid = ctx.alias.length() > 0; + } else { + ctx.byAlias = false; + ctx.deviceId = _deviceId; + ctx.remoteType = _remoteType; + ctx.groupId = _groupId; + ctx.valid = ctx.deviceId.length() > 0 && ctx.remoteType.length() > 0; + } + + return ctx; +} + +String MilightHubItem::buildTargetPath(const RequestContext& ctx) const { + if (!ctx.valid) { + return String(); + } + + if (ctx.byAlias) { + return String(F("/gateways/")) + encodeUriComponent(ctx.alias); + } + + String path(F("/gateways/")); + path += encodeUriComponent(ctx.deviceId); + path += '/'; + path += encodeUriComponent(ctx.remoteType); + path += '/'; + path += String(ctx.groupId); + return path; +} + +String MilightHubItem::buildUrl(const String& path, bool block, bool normalized) const { + String url(F("http://")); + url += _host; + if (_port != 80) { + url += ':'; + url += String(_port); + } + url += path; + + char separator = '?'; + if (block) { + url += separator; + url += F("blockOnQueue=true"); + separator = '&'; + } + if (normalized) { + url += separator; + url += F("fmt=normalized"); + } + return url; +} + +void MilightHubItem::ensureMqttSubscription() { + if (!_resolvedCommandTopic.length()) { + return; + } + if (!mqttIsConnect()) { + _mqttSubscribed = false; + return; + } + if (_mqttSubscribed) { + return; + } + mqttSubscribeExternal(_mqttCommandTopic, _mqttCommandAddPrefix); + _mqttSubscribed = true; +} + +bool MilightHubItem::performRequest(const String& method, const RequestContext& ctx, const String& payload, String& response, int& statusCode) { + if (!ctx.valid) { + SerialPrint("E", kTag, F("target not configured")); + return false; + } + + const String path = buildTargetPath(ctx); + const String url = buildUrl(path, ctx.blockOnQueue, ctx.normalized); + + WiFiClient client; + HTTPClient http; + +#if defined(ESP8266) || defined(ESP32) || defined(LIBRETINY) + if (!http.begin(client, url)) { +#else + if (!http.begin(url)) { +#endif + SerialPrint("E", kTag, String(F("HTTP begin failed: ")) + url); + return false; + } + + if (_username.length()) { + http.setAuthorization(_username.c_str(), _password.c_str()); + } + + if (_timeoutMs > 0) { + http.setTimeout(_timeoutMs); + } + + if (method == F("PUT") || method == F("POST")) { + http.addHeader(F("Content-Type"), F("application/json")); + } + + if (method == F("GET")) { + statusCode = http.GET(); + } else if (method == F("PUT")) { + statusCode = http.PUT(payload); + } else if (method == F("POST")) { + statusCode = http.POST(payload); + } else if (method == F("DELETE")) { + statusCode = http.sendRequest(F("DELETE")); + } else { + SerialPrint("E", kTag, String(F("unsupported method: ")) + method); + http.end(); + return false; + } + + response = http.getString(); + http.end(); + + if (statusCode < 0) { + SerialPrint("E", kTag, String(F("HTTP error: ")) + HTTPClient::errorToString(statusCode)); + return false; + } + + const bool success = statusCode >= 200 && statusCode < 300; + SerialPrint(success ? "i" : "E", kTag, method + ' ' + url + F(" -> ") + String(statusCode)); + + if (response.length()) { + SerialPrint("i", kTag, String(F("response: ")) + response); + } + + return success; +} + +void MilightHubItem::storeResponse(const String& payload, int statusCode, bool notify) { + value.isDecimal = false; + if (payload.length()) { + value.valS = payload; + } else { + value.valS = String(statusCode); + } + + publishMqttResponse(payload, statusCode); + + if (notify) { + regEvent(value.valS, kTag); + } +} + +bool MilightHubItem::sendUpdate(const String& rawPayload) { + if (!isNetworkActive()) { + SerialPrint("W", kTag, F("network offline")); + return false; + } + + String payload = rawPayload; + payload.trim(); + if (!payload.length()) { + return false; + } + + if (!(payload.startsWith("{") || payload.startsWith("["))) { + String normalized = payload; + normalized.trim(); + normalized.toUpperCase(); + if (normalized == F("ON") || normalized == F("OFF")) { + payload = String(F("{\"state\":\"")) + normalized + F("\"}"); + } else { + SerialPrint("E", kTag, F("set expects JSON payload or ON/OFF")); + return false; + } + } + + RequestContext ctx = currentContext(); + String response; + int status = 0; + const bool ok = performRequest(F("PUT"), ctx, payload, response, status); + storeResponse(response, status, _rememberResponse); + return ok; +} + +bool MilightHubItem::sendFieldUpdate(const String& field, const IoTValue& fieldValue) { + if (!field.length()) { + return false; + } + + const String payload = buildFieldPayload(field, fieldValue); + return sendUpdate(payload); +} + +bool MilightHubItem::sendCommandPayload(const String& payload) { + if (!payload.length()) { + return false; + } + + RequestContext ctx = currentContext(); + String response; + int status = 0; + const bool ok = performRequest(F("PUT"), ctx, payload, response, status); + storeResponse(response, status, _rememberResponse); + return ok; +} + +bool MilightHubItem::sendCommandsPayload(const String& payload) { + return sendCommandPayload(payload); +} + +bool MilightHubItem::requestState() { + if (!isNetworkActive()) { + SerialPrint("W", kTag, F("network offline")); + return false; + } + + RequestContext ctx = currentContext(); + ctx.blockOnQueue = false; // reading does not need block + String response; + int status = 0; + const bool ok = performRequest(F("GET"), ctx, String(), response, status); + storeResponse(response, status, true); + return ok; +} + +bool MilightHubItem::deleteState() { + if (!isNetworkActive()) { + SerialPrint("W", kTag, F("network offline")); + return false; + } + + RequestContext ctx = currentContext(); + String response; + int status = 0; + const bool ok = performRequest(F("DELETE"), ctx, String(), response, status); + storeResponse(response, status, _rememberResponse); + return ok; +} + +bool MilightHubItem::postRawCommand(const String& payload, const String& remoteTypeOverride) { + if (!isNetworkActive()) { + SerialPrint("W", kTag, F("network offline")); + return false; + } + + String remote = remoteTypeOverride.length() ? remoteTypeOverride : _remoteType; + if (!remote.length()) { + SerialPrint("E", kTag, F("remoteType required for raw command")); + return false; + } + + RequestContext ctx = currentContext(); + ctx.valid = true; + ctx.byAlias = false; + ctx.deviceId = String(); + ctx.remoteType = remote; + ctx.groupId = 0; + + const String path = String(F("/raw_commands/")) + encodeUriComponent(remote); + const String url = buildUrl(path, false, false); + + WiFiClient client; + HTTPClient http; + +#if defined(ESP8266) || defined(ESP32) || defined(LIBRETINY) + if (!http.begin(client, url)) { +#else + if (!http.begin(url)) { +#endif + SerialPrint("E", kTag, String(F("HTTP begin failed: ")) + url); + return false; + } + + if (_username.length()) { + http.setAuthorization(_username.c_str(), _password.c_str()); + } + if (_timeoutMs > 0) { + http.setTimeout(_timeoutMs); + } + http.addHeader(F("Content-Type"), F("application/json")); + + const int status = http.POST(payload); + const String response = http.getString(); + http.end(); + + if (status < 0) { + SerialPrint("E", kTag, String(F("HTTP error: ")) + HTTPClient::errorToString(status)); + return false; + } + + const bool ok = status >= 200 && status < 300; + SerialPrint(ok ? "i" : "E", kTag, String(F("POST ")) + url + F(" -> ") + String(status)); + if (response.length()) { + SerialPrint("i", kTag, String(F("response: ")) + response); + } + + storeResponse(response, status, _rememberResponse); + return ok; +} + +String MilightHubItem::getMqttExterSub() { + return _resolvedCommandTopic; +} + +void MilightHubItem::onMqttRecive(String& topic, String& msg) { + if (!_resolvedCommandTopic.length()) { + return; + } + if (topic != _resolvedCommandTopic) { + return; + } + if (msg.startsWith(F("HELLO"))) { + return; + } + handleMqttMessage(msg); +} + +String MilightHubItem::valueToJsonLiteral(const IoTValue& value) const { + if (value.isDecimal) { + return doubleToString(value.valD); + } + + String text = value.valS; + text.trim(); + if (!text.length()) { + return String(F("\"\"")); + } + + String lower = text; + lower.toLowerCase(); + if (lower == F("true") || lower == F("false")) { + return lower; + } + + if (isNumericString(text)) { + return text; + } + + if (text.startsWith("{") || text.startsWith("[") || + (text.startsWith("\"") && text.endsWith("\""))) { + return text; + } + + return String('"') + escapeJson(text) + '"'; +} + +String MilightHubItem::buildFieldPayload(const String& rawField, const IoTValue& value) const { + String field = rawField; + field.trim(); + if (!field.length()) { + return String(); + } + + String payload(F("{\"")); + payload += field; + payload += F("\":"); + payload += valueToJsonLiteral(value); + payload += '}'; + return payload; +} + +String MilightHubItem::buildCommandPayload(const String& command) const { + String trimmed = command; + trimmed.trim(); + if (!trimmed.length()) { + return String(); + } + + if (trimmed.startsWith("{")) { + return trimmed; + } + + if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) { + return String(F("{\"command\":")) + trimmed + '}'; + } + + return String(F("{\"command\":\"")) + escapeJson(trimmed) + F("\"}"); +} + +String MilightHubItem::buildCommandsPayload(const String& commands) const { + String trimmed = commands; + trimmed.trim(); + if (!trimmed.length()) { + return String(); + } + + if (trimmed.startsWith("[")) { + return String(F("{\"commands\":")) + trimmed + '}'; + } + + if (trimmed.startsWith("{")) { + return trimmed; + } + + return String(F("{\"commands\":[\"")) + escapeJson(trimmed) + F("\"]}")); +} + +void MilightHubItem::setAliasInternal(const String& alias) { + if (!alias.length()) { + SerialPrint("W", kTag, F("empty alias ignored")); + return; + } + _alias = alias; + _useAlias = true; + SerialPrint("i", kTag, String(F("alias set to ")) + _alias); +} + +void MilightHubItem::setDeviceInternal(const String& deviceId, const String& remoteType, int groupId) { + if (!deviceId.length() || !remoteType.length()) { + SerialPrint("E", kTag, F("deviceId and remoteType required")); + return; + } + _deviceId = deviceId; + _remoteType = remoteType; + if (groupId < 0) { + groupId = 0; + } + _groupId = groupId; + _useAlias = false; + SerialPrint("i", kTag, String(F("device target: ")) + _deviceId + F(" / ") + _remoteType + F(" / ") + String(_groupId)); +} + +void MilightHubItem::handleMqttMessage(String payload) { + payload.trim(); + if (!payload.length()) { + return; + } + + const size_t capacity = payload.length() + 64; + DynamicJsonDocument doc(capacity); + const DeserializationError err = deserializeJson(doc, payload); + if (!err) { + JsonVariantConst root = doc.as(); + if (handleJsonMqttCommand(root)) { + return; + } + } + + handlePlainMqttCommand(payload); +} + +bool MilightHubItem::handleJsonMqttCommand(JsonVariantConst root) { + if (root.is()) { + JsonObjectConst obj = root.as(); + String command = obj[F("command")].as(); + if (!command.length()) { + command = obj[F("action")].as(); + } + + if (!command.length()) { + String serialized; + serializeJson(obj, serialized); + sendUpdate(serialized); + return true; + } + + std::vector params; + if (obj.containsKey(F("params")) && obj[F("params")].is()) { + JsonArrayConst arr = obj[F("params")].as(); + for (JsonVariantConst entry : arr) { + params.push_back(jsonVariantToIoTValue(entry)); + } + } else { + if (obj.containsKey(F("payload"))) { + params.push_back(jsonVariantToIoTValue(obj[F("payload")])); + } else if (obj.containsKey(F("value"))) { + params.push_back(jsonVariantToIoTValue(obj[F("value")])); + } + } + + if (command.equalsIgnoreCase(F("setdevice")) && + obj.containsKey(F("deviceId")) && obj.containsKey(F("remoteType")) && obj.containsKey(F("groupId"))) { + params.clear(); + params.push_back(makeStringValue(obj[F("deviceId")].as())); + params.push_back(makeStringValue(obj[F("remoteType")].as())); + params.push_back(makeStringValue(obj[F("groupId")].as())); + } + + if (command.equalsIgnoreCase(F("setfield")) && obj.containsKey(F("field")) && obj.containsKey(F("value"))) { + params.clear(); + params.push_back(makeStringValue(obj[F("field")].as())); + params.push_back(jsonVariantToIoTValue(obj[F("value")])); + } else if (obj.containsKey(F("field"))) { + params.insert(params.begin(), makeStringValue(obj[F("field")].as())); + } + + if (command.equalsIgnoreCase(F("raw")) && obj.containsKey(F("remoteType")) && !params.empty()) { + params.push_back(makeStringValue(obj[F("remoteType")].as())); + } + + execute(command, params); + return true; + } + + if (root.is()) { + String serialized; + serializeJson(root, serialized); + sendUpdate(serialized); + return true; + } + + return false; +} + +void MilightHubItem::handlePlainMqttCommand(const String& payload) { + String lowered = payload; + lowered.toLowerCase(); + + if (lowered == F("get")) { + requestState(); + return; + } + if (lowered == F("deletestate") || lowered == F("delete")) { + deleteState(); + return; + } + if (lowered.startsWith(F("command "))) { + String commandPayload = payload.substring(8); + const String payloadJson = buildCommandPayload(commandPayload); + sendCommandPayload(payloadJson); + return; + } + if (lowered.startsWith(F("raw "))) { + String rawPayload = payload.substring(4); + postRawCommand(rawPayload, String()); + return; + } + + sendUpdate(payload); +} + +IoTValue MilightHubItem::jsonVariantToIoTValue(JsonVariantConst variant) const { + IoTValue result; + if (variant.is() || variant.is() || variant.is() || variant.is()) { + result.isDecimal = true; + result.valD = static_cast(variant.as()); + result.valS = doubleToString(result.valD); + return result; + } + + if (variant.is()) { + result.isDecimal = false; + result.valS = variant.as() ? F("true") : F("false"); + return result; + } + + if (variant.is()) { + result.isDecimal = false; + result.valS = variant.as(); + return result; + } + + result.isDecimal = false; + String serialized; + serializeJson(variant, serialized); + result.valS = serialized; + return result; +} + +IoTValue MilightHubItem::makeStringValue(const String& text) const { + IoTValue result; + result.isDecimal = false; + result.valS = text; + return result; +} + +void MilightHubItem::publishMqttResponse(const String& payload, int statusCode) { + if (!_resolvedResponseTopic.length()) { + return; + } + if (!mqttIsConnect()) { + return; + } + + String json(F("{}")); + jsonWriteInt_(json, F("status"), statusCode); + if (payload.length()) { + jsonWriteStr_(json, F("payload"), payload); + } + + if (!publish(_resolvedResponseTopic, json)) { + SerialPrint("W", kTag, F("MQTT response publish failed")); + } +} + +String MilightHubItem::resolveTopic(const String& topic, bool addPrefix) const { + if (!topic.length()) { + return String(); + } + if (!addPrefix) { + return topic; + } + return mqttPrefix + '/' + topic; +} + +IoTValue MilightHubItem::execute(String command, std::vector& param) { + IoTValue result; + + command.toLowerCase(); + + if (command == F("set")) { + if (!param.empty()) { + sendUpdate(toParamString(param[0])); + } + } else if (command == F("setfield")) { + if (param.size() >= 2) { + const String field = toParamString(param[0]); + sendFieldUpdate(field, param[1]); + } + } else if (command == F("command")) { + if (!param.empty()) { + const String payload = buildCommandPayload(toParamString(param[0])); + sendCommandPayload(payload); + } + } else if (command == F("commands")) { + if (!param.empty()) { + const String payload = buildCommandsPayload(toParamString(param[0])); + sendCommandsPayload(payload); + } + } else if (command == F("get")) { + requestState(); + } else if (command == F("deletestate")) { + deleteState(); + } else if (command == F("setalias")) { + if (!param.empty()) { + setAliasInternal(toParamString(param[0])); + } + } else if (command == F("setdevice")) { + if (param.size() >= 3) { + const String deviceId = toParamString(param[0]); + const String remoteType = toParamString(param[1]); + const String groupString = toParamString(param[2]); + int group = groupString.toInt(); + setDeviceInternal(deviceId, remoteType, group); + } + } else if (command == F("setblock")) { + if (!param.empty()) { + String value = toParamString(param[0]); + value = normalizeBoolString(value); + if (value == F("true") || value == F("1")) { + _blockOnQueue = true; + } else if (value == F("false") || value == F("0")) { + _blockOnQueue = false; + } + SerialPrint("i", kTag, String(F("blockOnQueue=")) + (_blockOnQueue ? F("true") : F("false"))); + } + } else if (command == F("setnormalized")) { + if (!param.empty()) { + String value = toParamString(param[0]); + value = normalizeBoolString(value); + if (value == F("true") || value == F("1")) { + _normalizedResponse = true; + } else if (value == F("false") || value == F("0")) { + _normalizedResponse = false; + } + SerialPrint("i", kTag, String(F("normalizedResponse=")) + (_normalizedResponse ? F("true") : F("false"))); + } + } else if (command == F("setstore")) { + if (!param.empty()) { + String value = toParamString(param[0]); + value = normalizeBoolString(value); + if (value == F("true") || value == F("1")) { + _rememberResponse = true; + } else if (value == F("false") || value == F("0")) { + _rememberResponse = false; + } + SerialPrint("i", kTag, String(F("storeResponse=")) + (_rememberResponse ? F("true") : F("false"))); + } + } else if (command == F("setauth")) { + if (param.size() >= 2) { + _username = toParamString(param[0]); + _password = toParamString(param[1]); + SerialPrint("i", kTag, F("basic auth credentials updated")); + } + } else if (command == F("settimeout")) { + if (!param.empty()) { + const int timeout = toParamString(param[0]).toInt(); + if (timeout > 0) { + _timeoutMs = static_cast(timeout); + SerialPrint("i", kTag, String(F("timeout=")) + String(_timeoutMs)); + } + } + } else if (command == F("sethost")) { + if (!param.empty()) { + _host = toParamString(param[0]); + SerialPrint("i", kTag, String(F("host=")) + _host); + } + } else if (command == F("setport")) { + if (!param.empty()) { + const int newPort = toParamString(param[0]).toInt(); + if (newPort > 0 && newPort <= 65535) { + _port = static_cast(newPort); + SerialPrint("i", kTag, String(F("port=")) + String(_port)); + } + } + } else if (command == F("raw")) { + if (!param.empty()) { + const String payload = toParamString(param[0]); + String remoteOverride; + if (param.size() >= 2) { + remoteOverride = toParamString(param[1]); + } + postRawCommand(payload, remoteOverride); + } + } + + return result; +} + +void* getAPI_MilightHub(String subtype, String param) { + if (subtype == F("MilightHub")) { + return new MilightHubItem(param); + } + return nullptr; +} diff --git a/src/modules/exec/MilightHub/modinfo.json b/src/modules/exec/MilightHub/modinfo.json new file mode 100644 index 00000000..369efa73 --- /dev/null +++ b/src/modules/exec/MilightHub/modinfo.json @@ -0,0 +1,150 @@ +{ + "menuSection": "executive_devices", + "configItem": [ + { + "global": 0, + "name": "MiLight Hub", + "type": "Writing", + "subtype": "MilightHub", + "id": "milight", + "widget": "", + "page": "Исполнители", + "descr": "Управление светом через esp8266_milight_hub REST API.", + "host": "milight-hub", + "port": 80, + "targetType": "alias", + "alias": "livingroom", + "deviceId": "0x0000", + "remoteType": "rgb_cct", + "groupId": 0, + "blockOnQueue": false, + "normalizedResponse": false, + "storeResponse": true, + "username": "", + "password": "", + "timeout": 5000, + "mqttCommandTopic": "milight/command", + "mqttCommandUsePrefix": true, + "mqttResponseTopic": "milight/response", + "mqttResponseUsePrefix": true + } + ], + "about": { + "descr": "Если задан mqttCommandTopic, модуль слушает указанный MQTT-топик. Ожидаемый формат сообщения: {\"command\":\"set\", \"params\":[...]} или {\"payload\":{...}}. Также понимает текстовые команды GET/DELETESTATE/command .../raw ...", + "authorContact": "https://iotmanager.org", + "authorGit": "https://github.com/IoTManagerProject", + "specialThanks": "https://github.com/sidoh/esp8266_milight_hub", + "moduleName": "MilightHub", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 16, + "esp8266_4mb": 16 + }, + "title": "ESP8266 MiLight Hub REST", + "moduleDesc": "Команды управления устройствами через REST API прошивки esp8266_milight_hub. Дополнительно может слушать MQTT-топик и пересылать ответы обратно в брокер.", + "propInfo": { + "host": "Имя хоста или IP адрес MiLight Hub.", + "port": "TCP порт REST API (по умолчанию 80).", + "targetType": "Режим адресации: alias или device.", + "alias": "Имя устройства, заданное в MiLight Hub (используется, если выбран режим alias).", + "deviceId": "Идентификатор устройства (0-65535 или 0xNNNN) для режима device.", + "remoteType": "Тип пульта/лампы (например rgb_cct) для режима device.", + "groupId": "Идентификатор группы (0-8) для режима device.", + "blockOnQueue": "Ждать отправку пакетов перед ответом (параметр blockOnQueue).", + "normalizedResponse": "Добавлять fmt=normalized и получать нормализованный ответ.", + "storeResponse": "Сохранять последний ответ в значении элемента.", + "username": "Логин для HTTP Basic Auth (если включен в MiLight Hub).", + "password": "Пароль для HTTP Basic Auth.", + "timeout": "Таймаут HTTP-запроса в миллисекундах.", + "mqttCommandTopic": "MQTT-топик, откуда модуль будет получать команды (пусто — без MQTT).", + "mqttCommandUsePrefix": "1 — добавлять глобальный mqttPrefix к топику команд, 0 — использовать как указан.", + "mqttResponseTopic": "MQTT-топик для публикации HTTP-ответов (пусто — не публиковать).", + "mqttResponseUsePrefix": "1 — добавлять глобальный mqttPrefix к топику ответов, 0 — использовать как указан." + }, + "funcInfo": [ + { + "name": "set", + "descr": "Отправить JSON с параметрами состояния (PUT /gateways/...). Для простоты поддерживает строки ON/OFF.", + "params": [ + "payload" + ] + }, + { + "name": "setField", + "descr": "Изменить одно поле состояния (brightness, kelvin и т.п.).", + "params": [ + "field", + "value" + ] + }, + { + "name": "command", + "descr": "Отправить команду (night_mode, pair и т.п.). Можно передать полный JSON объекта команды.", + "params": [ + "command" + ] + }, + { + "name": "commands", + "descr": "Отправить массив команд (key commands).", + "params": [ + "commandsArray" + ] + }, + { + "name": "get", + "descr": "Получить текущее состояние устройства (GET /gateways/...).", + "params": [] + }, + { + "name": "deleteState", + "descr": "Удалить сохранённое состояние устройства (DELETE /gateways/...).", + "params": [] + }, + { + "name": "setAlias", + "descr": "Изменить alias по умолчанию и включить режим alias.", + "params": [ + "alias" + ] + }, + { + "name": "setDevice", + "descr": "Изменить deviceId, тип пульта и группу по умолчанию и включить режим device.", + "params": [ + "deviceId", + "remoteType", + "groupId" + ] + }, + { + "name": "setBlock", + "descr": "Включить или выключить blockOnQueue для последующих запросов.", + "params": [ + "bool" + ] + }, + { + "name": "setNormalized", + "descr": "Настроить добавление fmt=normalized к запросам.", + "params": [ + "bool" + ] + }, + { + "name": "mqttControl", + "descr": "Если задан mqttCommandTopic, модуль слушает указанный MQTT-топик. Ожидаемый формат сообщения: {\"command\":\"set\", \"params\":[...]} или {\"payload\":{...}}. Также понимает текстовые команды GET/DELETESTATE/command .../raw ...", + "params": [ + "command", + "params|payload", + "field/value" + ] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [] + } +} diff --git a/src/modules/exec/Mp3/modinfo.json b/src/modules/exec/Mp3/modinfo.json index 631ba050..3494d9a5 100644 --- a/src/modules/exec/Mp3/modinfo.json +++ b/src/modules/exec/Mp3/modinfo.json @@ -87,11 +87,17 @@ } ] }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "dfrobot/DFRobotDFPlayerMini @ ^1.0.5" ], + "esp32c6_4mb": [ + "exclude" + ], + "esp32c6_8mb": [ + "exclude" + ], "esp82*": [ "dfrobot/DFRobotDFPlayerMini @ ^1.0.5" ] diff --git a/src/modules/exec/MySensors/modinfo.json b/src/modules/exec/MySensors/modinfo.json index 0eb297c8..ecd57d67 100644 --- a/src/modules/exec/MySensors/modinfo.json +++ b/src/modules/exec/MySensors/modinfo.json @@ -49,6 +49,12 @@ }, "defActive": false, "usedLibs": { + "esp32c6_4mb": [ + "exclude" + ], + "esp32c6_8mb": [ + "exclude" + ], "esp32*": [] } } \ No newline at end of file diff --git a/src/modules/exec/Pcf8574/modinfo.json b/src/modules/exec/Pcf8574/modinfo.json index f3ece10d..6a040c14 100644 --- a/src/modules/exec/Pcf8574/modinfo.json +++ b/src/modules/exec/Pcf8574/modinfo.json @@ -30,7 +30,7 @@ }, "title": "Расширитель портов Pcf8574" }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "adafruit/Adafruit BusIO @ ^1.13.2" diff --git a/src/modules/exec/Pwm32/Pwm32.cpp b/src/modules/exec/Pwm32/Pwm32.cpp index 79f6eedf..875595fe 100644 --- a/src/modules/exec/Pwm32/Pwm32.cpp +++ b/src/modules/exec/Pwm32/Pwm32.cpp @@ -8,15 +8,15 @@ extern IoTGpio IoTgpio; class Pwm32 : public IoTItem { - private: +private: int _pin; - int _freq; + int _freq; int _apin, _oldValue; bool _freezVal = true; - int _ledChannel; + int _ledChannel; int _resolution; - public: +public: Pwm32(String parameters): IoTItem(parameters) { _interval = _interval / 1000; // корректируем величину интервала int, теперь он в миллисекундах @@ -24,10 +24,14 @@ class Pwm32 : public IoTItem { jsonRead(parameters, "freq", _freq); jsonRead(parameters, "ledChannel", _ledChannel); jsonRead(parameters, "PWM_resolution", _resolution); - + pinMode(_pin, OUTPUT); +#if defined(esp32c6_4mb) || defined(esp32c6_8mb) + ledcAttachChannel(_pin, _freq, _resolution, _ledChannel); +#else ledcSetup(_ledChannel, _freq, _resolution); ledcAttachPin(_pin, _ledChannel); +#endif ledcWrite(_ledChannel, value.valD); _resolution = pow(2, _resolution); // переводим биты в значение @@ -53,7 +57,7 @@ class Pwm32 : public IoTItem { } } } - + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; ledcWrite(_ledChannel, value.valD); diff --git a/src/modules/exec/Pwm8266/modinfo.json b/src/modules/exec/Pwm8266/modinfo.json index 98163426..02a13034 100644 --- a/src/modules/exec/Pwm8266/modinfo.json +++ b/src/modules/exec/Pwm8266/modinfo.json @@ -38,7 +38,7 @@ "freq": "Частота" } }, - "defActive": true, + "defActive": false, "usedLibs": { "esp82*": [] } diff --git a/src/modules/exec/SIM800/modinfo.json b/src/modules/exec/SIM800/modinfo.json index 959879ed..aac9f890 100644 --- a/src/modules/exec/SIM800/modinfo.json +++ b/src/modules/exec/SIM800/modinfo.json @@ -78,8 +78,6 @@ }, "defActive": false, "usedLibs": { - "esp32_4mb": [], - "esp32_4mb3f": [], "esp32*": [], "esp82*": [] } diff --git a/src/modules/exec/SmartBoiler/modinfo.json b/src/modules/exec/SmartBoiler/modinfo.json index af2aa194..a5a1e861 100644 --- a/src/modules/exec/SmartBoiler/modinfo.json +++ b/src/modules/exec/SmartBoiler/modinfo.json @@ -164,7 +164,6 @@ }, "defActive": false, "usedLibs": { - "esp32_4mb3f": [], "esp32*": [], "esp82*": [] } diff --git a/src/modules/exec/Telegram/Telegram.cpp b/src/modules/exec/Telegram/Telegram.cpp index da7186af..3dfc3a9a 100644 --- a/src/modules/exec/Telegram/Telegram.cpp +++ b/src/modules/exec/Telegram/Telegram.cpp @@ -19,21 +19,21 @@ class Telegram : public IoTItem { msg = deleteBeforeDelimiter(msg, "_"); generateOrder(selectToMarker(msg, "_"), selectToMarkerLast(msg, "_")); _myBot.sendMessage(_chatID, "order done"); - SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + String(msg)); + SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + String(msg)); } else if (msg.indexOf("get") != -1) { msg = deleteBeforeDelimiter(msg, "_"); IoTItem* item = findIoTItem(msg); if (item) { _myBot.sendMessage(_chatID, item->getValue()); - SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + String(msg)); + SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + String(msg)); } } else if (msg.indexOf("all") != -1) { String list = returnListOfParams(); _myBot.sendMessage(_chatID, list); - SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + "\n" + list); + SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + "\n" + list); } else if (msg.indexOf("help") != -1) { _myBot.sendMessage(_chatID, "ID: " + chipId); - _myBot.sendMessage(_chatID, "chatID: " + uint64ToString(_chatID)); + _myBot.sendMessage(_chatID, "chatID: " + uint64ToStringIoTM(_chatID)); _myBot.sendMessage(_chatID, F("Wrong order, use /all to get all values, /get_id to get value, or /set_id_value to set value")); } else { setValue(msg); @@ -70,7 +70,7 @@ class Telegram : public IoTItem { if (_receiveMsg) { TBMessage msg; if (_myBot.getNewMessage(msg)) { - SerialPrint("->", F("Telegram"), "chat ID: " + uint64ToString(msg.sender.id) + ", msg: " + msg.text); + SerialPrint("->", F("Telegram"), "chat ID: " + uint64ToStringIoTM(msg.sender.id) + ", msg: " + msg.text); if (_autos) { _chatID = msg.sender.id; } @@ -100,12 +100,12 @@ class Telegram : public IoTItem { void sendTelegramMsg(bool often, String msg) { if (often) { _myBot.sendMessage(_chatID, msg); - SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + msg); + SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + msg); } else { if (_prevMsg != msg) { _prevMsg = msg; _myBot.sendMessage(_chatID, msg); - SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + msg); + SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToStringIoTM(_chatID) + ", msg: " + msg); } } } diff --git a/src/modules/exec/TelegramLT/TelegramLT.cpp b/src/modules/exec/TelegramLT/TelegramLT.cpp index 44a7828d..517fa6d4 100644 --- a/src/modules/exec/TelegramLT/TelegramLT.cpp +++ b/src/modules/exec/TelegramLT/TelegramLT.cpp @@ -41,7 +41,7 @@ class TelegramLT : public IoTItem { if (param.size() == 1) { String strTmp; if (param[0].isDecimal && param[0].valS == "") - strTmp = param[0].valD; + strTmp = String(param[0].valD); else strTmp = param[0].valS; diff --git a/src/modules/exec/TelegramLT/modinfo.json b/src/modules/exec/TelegramLT/modinfo.json index b8cf7ba5..a0d79d33 100644 --- a/src/modules/exec/TelegramLT/modinfo.json +++ b/src/modules/exec/TelegramLT/modinfo.json @@ -52,6 +52,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/exec/Telegram_v2/Telegram_v2.cpp b/src/modules/exec/Telegram_v2/Telegram_v2.cpp index b872730a..9e061424 100644 --- a/src/modules/exec/Telegram_v2/Telegram_v2.cpp +++ b/src/modules/exec/Telegram_v2/Telegram_v2.cpp @@ -11,6 +11,7 @@ #ifdef ESP8266 #define FB_DYNAMIC #endif + #include #include @@ -94,8 +95,8 @@ class Telegram_v2 : public IoTItem _myBot->tick(); if (fl_rollback) { - _myBot->tickManual(); // Чтобы отметить сообщение прочитанным #ifdef ESP32 + _myBot->tickManual(); // Чтобы отметить сообщение прочитанным if (Update.rollBack()) { SerialPrint("I", F("Update"), F("Откат OTA успешно выполнен")); @@ -107,6 +108,9 @@ class Telegram_v2 : public IoTItem SerialPrint("E", F("Update"), F("Откат OTA не выполнен!")); _myBot->sendMessage("Откат OTA не выполнен!", _chatID); } +#else + SerialPrint("I", F("Update"), F("Откат OTA только в ESP32")); + _myBot->sendMessage("Откат OTA поддерживается только в ESP32", _chatID); #endif } // была попытка OTA обновления. Обновляемся после ответа серверу! @@ -686,7 +690,7 @@ class Telegram_v2 : public IoTItem { _myBot->sendMessage("ID: " + chipId, _chatID); _myBot->sendMessage("chatID: " + _chatID, _chatID); - _myBot->sendMessage("Command: /help - this text \n /all - inline menu get all values \n /allMenu - bottom menu get all values \n /menu - bottom USER menu from scenario \n /get_id - get value by ID \n /set_id_value - set value in ID \n /file_name_type - take file from esp \n /file_type - support file type \n /reboot - reboot esp \n\n send file and write download - \"download\" file to esp \n\n send *.tft file - flash Nextion \n\n send firmware.bin or littltfs.bin - firmware ESP ", _chatID); + _myBot->sendMessage("Command: /help - this text \n /all - inline menu get all values \n /allMenu - bottom menu get all values \n /menu - bottom USER menu from scenario \n /get_id - get value by ID \n /set_id_value - set value in ID \n /file_/path/name_type - take file from esp \n /file_type - support file type \n /reboot - reboot esp \n\n send file and write download - \"download\" file to esp \n\n send *.tft file - flash Nextion \n\n send firmware.bin or littltfs.bin - firmware ESP ", _chatID); } } else if (msg.text.indexOf("/reboot") != -1) diff --git a/src/modules/exec/Thermostat/GyverPID.h b/src/modules/exec/Thermostat/GyverPID.h new file mode 100644 index 00000000..5adddeea --- /dev/null +++ b/src/modules/exec/Thermostat/GyverPID.h @@ -0,0 +1,171 @@ +/* + GyverPID - библиотека PID регулятора для Arduino + Документация: https://alexgyver.ru/gyverpid/ + GitHub: https://github.com/GyverLibs/GyverPID + Возможности: + - Время одного расчёта около 70 мкс + - Режим работы по величине или по её изменению (для интегрирующих процессов) + - Возвращает результат по встроенному таймеру или в ручном режиме + - Встроенные калибровщики коэффициентов + - Режим работы по ошибке и по ошибке измерения + - Встроенные оптимизаторы интегральной суммы + + AlexGyver, alex@alexgyver.ru + https://alexgyver.ru/ + MIT License + + Версии: + v1.1 - убраны дефайны + v1.2 - возвращены дефайны + v1.3 - вычисления ускорены, библиотека облегчена + v2.0 - логика работы чуть переосмыслена, код улучшен, упрощён и облегчён + v2.1 - integral вынесен в public + v2.2 - оптимизация вычислений + v2.3 - добавлен режим PID_INTEGRAL_WINDOW + v2.4 - реализация внесена в класс + v3.0 + - Добавлен режим оптимизации интегральной составляющей (см. доку) + - Добавлены автоматические калибровщики коэффициентов (см. примеры и доку) + v3.1 - исправлен режиме ON_RATE, добавлено автоограничение инт. суммы + v3.2 - чуть оптимизации, добавлена getResultNow + v3.3 - в тюнерах можно передать другой обработчик класса Stream для отладки +*/ + +#ifndef GyverPID_h +#define GyverPID_h +#include + +#if defined(PID_INTEGER) // расчёты с целыми числами +typedef int datatype; +#else // расчёты с float числами +typedef float datatype; +#endif + +#define NORMAL 0 +#define REVERSE 1 +#define ON_ERROR 0 +#define ON_RATE 1 + +class GyverPID +{ +public: + // ==== datatype это float или int, в зависимости от выбранного (см. пример integer_calc) ==== + GyverPID() {} + + // kp, ki, kd, dt + GyverPID(float new_kp, float new_ki, float new_kd, int new_dt = 60) + { + setDt(new_dt); + Kp = new_kp; + Ki = new_ki; + Kd = new_kd; + prevInput = 0; + integral = 0; + output = 0; + } + + // направление регулирования: NORMAL (0) или REVERSE (1) + void setDirection(boolean direction) + { + _direction = direction; + } + + // режим: работа по входной ошибке ON_ERROR (0) или по изменению ON_RATE (1) + void setMode(boolean mode) + { + _mode = mode; + } + + // лимит выходной величины (например для ШИМ ставим 0-255) + void setLimits(int min_output, int max_output) + { + _minOut = min_output; + _maxOut = max_output; + } + + // установка времени дискретизации (для getResultTimer) + void setDt(int new_dt) + { + _dt_s = new_dt; + _dt = new_dt * 1000; + } + + datatype setpoint = 0; // заданная величина, которую должен поддерживать регулятор + datatype input = 0; // сигнал с датчика (например температура, которую мы регулируем) + datatype output = 0; // выход с регулятора на управляющее устройство (например величина ШИМ или угол поворота серво) + float Kp = 0.0; // коэффициент P + float Ki = 0.0; // коэффициент I + float Kd = 0.0; // коэффициент D + float integral = 0.0; // интегральная сумма + + // возвращает новое значение при вызове (если используем свой таймер с периодом dt!) + datatype getResult() + { + datatype error = setpoint - input; // ошибка регулирования + datatype delta_input = prevInput - input; // изменение входного сигнала за dt + prevInput = input; // запомнили предыдущее + if (_direction) + { // смена направления + error = -error; + delta_input = -delta_input; + } + output = _mode ? 0 : (error * Kp); // пропорциональая составляющая + output += delta_input * Kd / _dt_s; // дифференциальная составляющая +#if (PID_INTEGRAL_WINDOW > 0) + // ЭКСПЕРИМЕНТАЛЬНЫЙ РЕЖИМ ИНТЕГРАЛЬНОГО ОКНА + if (++t >= PID_INTEGRAL_WINDOW) + t = 0; // перемотка t + integral -= errors[t]; // вычитаем старое + errors[t] = error * Ki * _dt_s; // запоминаем в массив + integral += errors[t]; // прибавляем новое +#else + integral += error * Ki * _dt_s; // обычное суммирование инт. суммы +#endif + +#ifdef PID_OPTIMIZED_I + // ЭКСПЕРИМЕНТАЛЬНЫЙ РЕЖИМ ОГРАНИЧЕНИЯ ИНТЕГРАЛЬНОЙ СУММЫ + output = constrain(output, _minOut, _maxOut); + if (Ki != 0) + integral = constrain(integral, (_minOut - output) / (Ki * _dt_s), (_maxOut - output) / (Ki * _dt_s)); +#endif + + if (_mode) + integral += delta_input * Kp; // режим пропорционально скорости + integral = constrain(integral, _minOut, _maxOut); // ограничиваем инт. сумму + output += integral; // интегральная составляющая + output = constrain(output, _minOut, _maxOut); // ограничиваем выход + return output; + } + + // возвращает новое значение не ранее, чем через dt миллисекунд (встроенный таймер с периодом dt) + datatype getResultTimer() + { + if (millis() - pidTimer >= _dt) + { + pidTimer = millis(); + getResult(); + } + return output; + } + + // посчитает выход по реальному прошедшему времени между вызовами функции + datatype getResultNow() + { + setDt(millis() - pidTimer); + pidTimer = millis(); + return getResult(); + } + +private: + int16_t _dt = 100; // время итерации в мс + float _dt_s = 0.1; // время итерации в с + boolean _mode = 0, _direction = 0; + int _minOut = 0, _maxOut = 255; + datatype prevInput = 0; + uint32_t pidTimer = 0; +#if (PID_INTEGRAL_WINDOW > 0) + datatype errors[PID_INTEGRAL_WINDOW]; + int t = 0; +#endif +}; +#endif \ No newline at end of file diff --git a/src/modules/exec/Thermostat/Thermostat.cpp b/src/modules/exec/Thermostat/Thermostat.cpp index 8d3c9260..d91000ab 100644 --- a/src/modules/exec/Thermostat/Thermostat.cpp +++ b/src/modules/exec/Thermostat/Thermostat.cpp @@ -1,5 +1,6 @@ #include "Global.h" #include "classes/IoTItem.h" +#include "GyverPID.h" extern IoTGpio IoTgpio; @@ -15,6 +16,7 @@ class ThermostatGIST : public IoTItem float sp, pv, pv2; String interim; int enable = 1; + int _direction = 0; public: ThermostatGIST(String parameters) : IoTItem(parameters) @@ -24,6 +26,7 @@ class ThermostatGIST : public IoTItem jsonRead(parameters, "term_rezerv_id", _term_rezerv_id); jsonRead(parameters, "gist", _gist); jsonRead(parameters, "rele", _rele); + jsonRead(parameters, "direction", _direction); } void doByInterval() @@ -55,13 +58,31 @@ class ThermostatGIST : public IoTItem { tmp = findIoTItem(_rele); if (tmp) - tmp->setValue("0", true); + { + if (_direction) + { + tmp->setValue("0", true); + } + else + { + tmp->setValue("1", true); + } + } } if (pv <= sp - _gist && enable) { tmp = findIoTItem(_rele); if (tmp) - tmp->setValue("1", true); + { + if (_direction) + { + tmp->setValue("1", true); + } + else + { + tmp->setValue("0", true); + } + } } } else @@ -86,13 +107,31 @@ class ThermostatGIST : public IoTItem { tmp = findIoTItem(_rele); if (tmp) - tmp->setValue("0", true); + { + if (_direction) + { + tmp->setValue("0", true); + } + else + { + tmp->setValue("1", true); + } + } } if (pv2 <= sp - _gist && enable) { tmp = findIoTItem(_rele); if (tmp) - tmp->setValue("1", true); + { + if (_direction) + { + tmp->setValue("1", true); + } + else + { + tmp->setValue("0", true); + } + } } } else @@ -144,23 +183,40 @@ class ThermostatGIST : public IoTItem return {}; } - ~ThermostatGIST(){}; + ~ThermostatGIST() {}; }; +GyverPID *regulator = nullptr; +GyverPID *instanceregulator(float _KP, float _KI, float _KD, int interval, boolean setDirection, int setLimitsMIN, int setLimitsMAX) +{ + if (!regulator) + { // Если библиотека ранее инициализировалась, то просто вернем указатель + // Инициализируем библиотеку + regulator = new GyverPID(_KP, _KI, _KD, interval); // коэф. П, коэф. И, коэф. Д, период дискретизации dt (с) + regulator->setDirection(setDirection); // направление регулирования (NORMAL/REVERSE). ПО УМОЛЧАНИЮ СТОИТ NORMAL + regulator->setLimits(setLimitsMIN, setLimitsMAX); // пределы. ПО УМОЛЧАНИЮ СТОЯТ 0 И 100 + SerialPrint("i", F("ThermostatPID"), " _KP:" + String(_KP) + " _KI:" + String(_KI) + " _KD:" + String(_KD) + " interval:" + String(interval) + " _setLimitsMIN:" + String(setLimitsMIN) + " _setLimitsMAX:" + String(setLimitsMAX) + " Direction:" + String(setDirection)); + // GyverPID regulator(_KP, _KI, _KD, interval); + } + return regulator; +} + class ThermostatPID : public IoTItem { private: - String _set_id; // заданная температура - String _term_id; // термометр - String _term_rezerv_id; // резервный термометр - float _int, _KP, _KI, _KD, sp, pv, + String _set_id; // заданная температура + String _term_id; // термометр + boolean _setDirection; + + float _int, _KP, _KI, _KD, + sp, pv, pv_last = 0, // предыдущая температура ierr = 0, // интегральная погрешность dt = 0; // время между измерениями String _rele; // реле String interim; int enable = 1; - long interval; + int interval, _setLimitsMIN, _setLimitsMAX; IoTItem *tmp; int releState = 0; @@ -169,20 +225,29 @@ class ThermostatPID : public IoTItem { jsonRead(parameters, "set_id", _set_id); jsonRead(parameters, "term_id", _term_id); - jsonRead(parameters, "term_rezerv_id", _term_rezerv_id); jsonRead(parameters, "int", _int); jsonRead(parameters, "KP", _KP); jsonRead(parameters, "KI", _KI); jsonRead(parameters, "KD", _KD); jsonRead(parameters, F("int"), interval); - interval = interval * 1000; // интервал проверки в сек jsonRead(parameters, "rele", _rele); + + // GyverPID + jsonRead(parameters, "setDirection", _setDirection); + jsonRead(parameters, "setLimitsMIN", _setLimitsMIN); + jsonRead(parameters, "setLimitsMAX", _setLimitsMAX); + + // в процессе работы можно менять коэффициенты + // instanceregulator(_KP, _KI, _KD, interval)->Kp = _KP; + // instanceregulator(_KP, _KI, _KD, interval)->Ki = _KI; + // instanceregulator(_KP, _KI, _KD, interval)->Kd = _KD; } protected: //=============================================================== // Вычисляем температуру контура отпления, коэффициенты ПИД регулятора //=============================================================== + /* float pid(float sp, float pv, float pv_last, float &ierr, float dt) { float Kc = _KP; // K / %Heater 5 @@ -217,11 +282,10 @@ class ThermostatPID : public IoTItem // выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла) op = constrain(op, oplo, ophi); } - ierr = I; return op; } - +*/ void doByInterval() { @@ -239,32 +303,24 @@ class ThermostatPID : public IoTItem interim = tmp->getValue(); pv = ::atof(interim.c_str()); } - if (pv < -40 && pv > 120 && !pv) // Решаем что ошибка датчика + if (enable) { - if (_term_rezerv_id != "") - { - tmp = findIoTItem(_term_rezerv_id); // используем резервный - if (tmp) - { - interim = tmp->getValue(); - pv = ::atof(interim.c_str()); - if (pv < -40 && pv > 120 && !pv) - pv = 0; - } - else - pv = 0; - } - else - pv = 0; + // regEvent(pid(sp, pv, pv_last, ierr, _int), "ThermostatPID", false, true); + // instanceregulator(_KP, _KI, _KD, interval,_setDirection,_setLimitsMIN,_setLimitsMAX)->setDirection(_setDirection); // направление регулирования (NORMAL/REVERSE). ПО УМОЛЧАНИЮ СТОИТ NORMAL + // instanceregulator(_KP, _KI, _KD, interval,_setDirection,_setLimitsMIN,_setLimitsMAX)->setLimits(_setLimitsMIN, _setLimitsMAX); // пределы. ПО УМОЛЧАНИЮ СТОЯТ 0 И 100 + // instanceregulator(_KP, _KI, _KD, interval)->setMode(1); + instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX)->setpoint = sp; + instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX)->input = pv; + value.valD = instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX)->getResult(); + SerialPrint("i", F("ThermostatPID"), " _KP:" + String(_KP) + " _KI:" + String(_KI) + " _KD:" + String(_KD) + " interval:" + String(interval) + " _setLimitsMIN:" + String(_setLimitsMIN) + " _setLimitsMAX:" + String(_setLimitsMAX) + " Direction:" + String(_setDirection)); + SerialPrint("i", F("ThermostatPID"), "setpoint: " + String(sp) + " input: " + String(pv)); + regEvent(value.valD, "ThermostatPID", false, true); } - if (sp && pv) + else { - // value.valD = pid(sp, pv, pv_last, ierr, _int); - // value.valS = (String)(int)value.valD; - regEvent(pid(sp, pv, pv_last, ierr, _int), "ThermostatPID", false, true); + value.valD = 0; + regEvent(value.valD, "ThermostatPID", false, true); } - else - regEvent(0, "ThermostatPID", false, true); pv_last = pv; } @@ -280,14 +336,14 @@ class ThermostatPID : public IoTItem currentMillis = millis(); difference = currentMillis - prevMillis; - if (_rele != "" && enable && value.valD * interval / 100000 > difference / 1000 && releState == 0) + if (_rele != "" && enable && value.valD * interval / 100 > difference / 1000 && releState == 0) { releState = 1; tmp = findIoTItem(_rele); if (tmp) tmp->setValue("1", true); } - if (_rele != "" && enable && value.valD * interval / 100000 < difference / 1000 && releState == 1) + if (_rele != "" && enable && value.valD * interval / 100 < difference / 1000 && releState == 1) { releState = 0; tmp = findIoTItem(_rele); @@ -295,7 +351,7 @@ class ThermostatPID : public IoTItem tmp->setValue("0", true); } - if (difference >= interval) + if (difference >= interval * 1000) { prevMillis = millis(); this->doByInterval(); @@ -311,6 +367,32 @@ class ThermostatPID : public IoTItem if (param.size()) { enable = param[0].valD; + if (enable == 0) + { + delete regulator; + regulator = nullptr; + // instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX); + } + } + } + if (command == "setLimitsMIN") + { + if (param.size()) + { + _setLimitsMIN = param[0].valD; + // delete regulator; + // regulator = nullptr; + // instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX); + } + } + if (command == "setLimitsMAX") + { + if (param.size()) + { + _setLimitsMAX = param[0].valD; + // delete regulator; + // regulator = nullptr; + // instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX); } } if (command == "KP") @@ -318,6 +400,9 @@ class ThermostatPID : public IoTItem if (param.size()) { _KP = param[0].valD; + delete regulator; + regulator = nullptr; + instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX); } } if (command == "KI") @@ -325,6 +410,9 @@ class ThermostatPID : public IoTItem if (param.size()) { _KI = param[0].valD; + delete regulator; + regulator = nullptr; + instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX); } } if (command == "KD") @@ -332,12 +420,30 @@ class ThermostatPID : public IoTItem if (param.size()) { _KD = param[0].valD; + delete regulator; + regulator = nullptr; + instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX); + } + } + + if (command == "setDirection") + { + if (param.size()) + { + _setDirection = param[0].valD; + delete regulator; + regulator = nullptr; + instanceregulator(_KP, _KI, _KD, interval, _setDirection, _setLimitsMIN, _setLimitsMAX); } } } return {}; } - ~ThermostatPID(){}; + ~ThermostatPID() + { + delete regulator; + regulator = nullptr; + }; }; class ThermostatETK : public IoTItem @@ -395,7 +501,7 @@ class ThermostatETK : public IoTItem } } - ~ThermostatETK(){}; + ~ThermostatETK() {}; }; class ThermostatETK2 : public IoTItem @@ -471,7 +577,7 @@ class ThermostatETK2 : public IoTItem } } - ~ThermostatETK2(){}; + ~ThermostatETK2() {}; }; void *getAPI_Thermostat(String subtype, String param) @@ -495,4 +601,4 @@ void *getAPI_Thermostat(String subtype, String param) //} return nullptr; -} +} \ No newline at end of file diff --git a/src/modules/exec/Thermostat/modinfo.json b/src/modules/exec/Thermostat/modinfo.json index fa0a274c..fe3ab759 100644 --- a/src/modules/exec/Thermostat/modinfo.json +++ b/src/modules/exec/Thermostat/modinfo.json @@ -16,8 +16,9 @@ "set_id": "", "term_id": "", "term_rezerv_id": "", - "gist": 0.3, - "rele": "" + "gist": 0.1, + "rele": "", + "direction": 0 }, { "global": 0, @@ -31,14 +32,16 @@ "descr": "термостат", "int": 60, "round": 1, - "map": "1,100,1,100", + "map": "1024,1024,1,100", "set_id": "", "term_id": "", - "term_rezerv_id": "", "rele": "", - "KP": 5.0, - "KI": 50, - "KD": 1.0 + "setDirection": 0, + "setLimitsMIN": 0, + "setLimitsMAX": 100, + "KP": 10, + "KI": 0.02, + "KD": 8 }, { "global": 0, @@ -75,11 +78,11 @@ ], "about": { "authorName": "AVAKS", - "authorContact": "https://t.me/@avaks_dev", + "authorContact": "https://t.me/@avaks", "authorGit": "https://github.com/avaksru", "specialThanks": "@Serghei63 за работу PID с обычным реле, Serg помощь в тестировании и устранении ошибок", "moduleName": "Thermostat", - "moduleVersion": "1", + "moduleVersion": "3", "usedRam": { "esp32_4mb": 15, "esp8266_4mb": 15 @@ -129,10 +132,24 @@ "params": [ "thermostat.KD(1) - задает значение коэффициента" ] + }, + { + "name": "setLimitsMIN / setLimitsMAX", + "descr": " лимит выходной величины (например для ШИМ ставим 0-255).", + "params": [ + "thermostat.setLimitsMIN(1) - задает минимальное значение PID" + ] + }, + { + "name": "setDirection", + "descr": "направление регулирования: NORMAL (0) или REVERSE (1).", + "params": [ + "thermostat.setDirection(1) - задает реверсное регулирование" + ] } ] }, - "defActive": false, + "defActive": true, "usedLibs": { "esp32*": [], "esp82*": [] diff --git a/src/modules/sceninfo.json b/src/modules/sceninfo.json index 551f6d0e..bdc834e0 100644 --- a/src/modules/sceninfo.json +++ b/src/modules/sceninfo.json @@ -7,13 +7,25 @@ "moduleName": "Scenario", "moduleVersion": "1.0", "title": "Сценарии", - "moduleDesc": "Сценарии позволяют реализовать индивидуальный алгоритм работы контроллера с учетом происходящих событий. Они представляют из себя описательный язык того, что нужно сделать при наступлении того или иного события, учитывая конкретные условия. \nВ базе языка - выражение вида: “Если условие истина, то выполнить одно действие, а если нет, то иное”. При этом проверка такого выражения будет осуществляться только при наступлении события связанного с элементом конфигурации, который упоминается в этом выражении. \nУсловием или действием может быть любое разрешенное выражение. Они все при выполнении возвращают значение. Выражение может состоять из: идентификаторов элементов конфигурации, чисел (целые, дробные и отрицательные), строк в кавычках, операций сравнения < > <= >= == !=, операций присваивания значений =, математических операций +-*/, логических операций &|, комментариев после символа #, функций (в параметрах которых так же могут быть любые разрешенные выражения), конструкции ветвления IfThenElse, группирующие блоки выражений {}", + "moduleDesc": "Сценарии позволяют реализовать индивидуальный алгоритм работы контроллера с учетом происходящих событий. Они представляют из себя описательный язык того, что нужно сделать при наступлении того или иного события, учитывая конкретные условия. \nВ базе языка - выражение вида: “Если условие истина, то выполнить одно действие, а если нет, то иное”. При этом проверка такого выражения будет осуществляться только при наступлении события связанного с элементом конфигурации, который упоминается в этом выражении. \nУсловием или действием может быть любое разрешенное выражение. Они все при выполнении возвращают значение. Выражение может состоять из: идентификаторов элементов конфигурации, чисел (целые, дробные и отрицательные), строк в кавычках, операций сравнения < > <= >= == !=, операций присваивания значений =, математических операций +-*/, логических операций &|, комментариев после символа #, функций (в параметрах которых так же могут быть любые разрешенные выражения), конструкции ветвления IfThenElse, группирующие блоки выражений {}, экранирование символа кавычек и переноса строки", "funcInfo": [ { "name": "getIntFromNet", "descr": "Получаем количество секунд доверия к значениям элемента. При -2 доверие полное, при -1 время доверия истекло. При >0 время обратного отсчета. Используется только совместно с ИД элемента: ID.getIntFromNet()", "params": [] }, + { + "name": "setInterval", + "descr": "Меняем интервал выполнения периодиеских операций элемента в секундах. Используется только совместно с ИД элемента: ID.setInterval(5)", + "params": ["Секунды"], + "return": "установленный интервал" + }, + { + "name": "doByInterval", + "descr": "Выполняем интервальное действие модуля вне плана. Используется только совместно с ИД элемента: ID.doByInterval()", + "params": [], + "return": "значение элемента после выполнения doByInterval" + }, { "name": "exit", "descr": "Прерываем работу сценария и выводим в консоль причину. Причина не обязательна.", diff --git a/src/modules/sensors/A02Distance/modinfo.json b/src/modules/sensors/A02Distance/modinfo.json index ba970862..85ac0c70 100644 --- a/src/modules/sensors/A02Distance/modinfo.json +++ b/src/modules/sensors/A02Distance/modinfo.json @@ -35,7 +35,7 @@ }, "title": "A0221AU, A02YYUW Ультразвуковой датчик дальности" }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [], "esp82*": [] diff --git a/src/modules/sensors/Acs712/modinfo.json b/src/modules/sensors/Acs712/modinfo.json index b741d377..c542e2c1 100644 --- a/src/modules/sensors/Acs712/modinfo.json +++ b/src/modules/sensors/Acs712/modinfo.json @@ -39,7 +39,7 @@ }, "title": "Acs712 Датчик тока" }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [], "esp82*": [] diff --git a/src/modules/sensors/AhtXX/AhtXX.cpp b/src/modules/sensors/AhtXX/AhtXX.cpp index a49e8fe1..7bac81c1 100644 --- a/src/modules/sensors/AhtXX/AhtXX.cpp +++ b/src/modules/sensors/AhtXX/AhtXX.cpp @@ -88,7 +88,7 @@ void* getAPI_AhtXX(String subtype, String param) { if (ahts.find(addr) == ahts.end()) { int shtType; - jsonRead(param, "type", shtType); + jsonRead(param, "shtType", shtType); ahts[addr] = new AHTxx(hexStringToUint8(addr), (AHTXX_I2C_SENSOR)shtType); diff --git a/src/modules/sensors/AnalogAdc/AnalogAdc.cpp b/src/modules/sensors/AnalogAdc/AnalogAdc.cpp index 4ee91d51..1df0f770 100644 --- a/src/modules/sensors/AnalogAdc/AnalogAdc.cpp +++ b/src/modules/sensors/AnalogAdc/AnalogAdc.cpp @@ -7,33 +7,41 @@ extern IoTGpio IoTgpio; // для добавления сенсора вам нужно скопировать этот файл и заменить в нем текст AnalogAdc на название вашего сенсора // Название должно быть уникальным, коротким и отражать суть сенсора. -//ребенок - родитель -class AnalogAdc : public IoTItem { - private: +// ребенок - родитель +class AnalogAdc : public IoTItem +{ +private: //======================================================================================================= // Секция переменных. // Это секция где Вы можете объявлять переменные и объекты arduino библиотек, что бы // впоследствии использовать их в loop и setup unsigned int _pin; - unsigned int _avgSteps, _avgCount; - unsigned long _avgSumm; - float adCresult; + unsigned int _avgSteps; + unsigned int realSteps; + float _adcAverage = 0; + unsigned int _period = 0; + unsigned long _lastSoundingMillis = 0; - public: +public: //======================================================================================================= // setup() // это аналог setup из arduino. Здесь вы можете выполнять методы инициализации сенсора. // Такие как ...begin и подставлять в них параметры полученные из web интерфейса. // Все параметры хранятся в перемененной parameters, вы можете прочитать любой параметр используя jsonRead функции: // jsonReadStr, jsonReadBool, jsonReadInt - AnalogAdc(String parameters) : IoTItem(parameters) { + AnalogAdc(String parameters) : IoTItem(parameters) + { _pin = jsonReadInt(parameters, "pin"); _avgSteps = jsonReadInt(parameters, "avgSteps"); - if (!_avgSteps) { + if (!_avgSteps) + { jsonRead(parameters, F("int"), _interval, false); } - _avgSumm = 0; - _avgCount = 0; + else + { + _period = _interval / _avgSteps; + } + // Serial.println("_period = " + String(_period)); } //======================================================================================================= @@ -44,10 +52,19 @@ class AnalogAdc : public IoTItem { // если у сенсора несколько величин то делайте несколько regEvent // не используйте delay - помните, что данный loop общий для всех модулей. Если у вас планируется длительная операция, постарайтесь разбить ее на порции // и выполнить за несколько тактов - void doByInterval() { - if (_avgSteps <= 1) value.valD = IoTgpio.analogRead(_pin); - value.valD = adCresult;/// - regEvent(value.valD, "AnalogAdc"); //обязательный вызов хотяб один + void doByInterval() + { + if (_avgSteps <= 1) + value.valD = IoTgpio.analogRead(_pin); + else + { + value.valD = _adcAverage / (float)realSteps; + //Serial.print("value= " + String(value.valD) + " \t"); + //Serial.print("adcAverage = " + String(_adcAverage) + " \t"); + //Serial.println("realSteps = " + String(realSteps)); + realSteps = 0; + } + regEvent(value.valD, "AnalogAdc"); // обязательный вызов хотяб один } //======================================================================================================= @@ -55,17 +72,20 @@ class AnalogAdc : public IoTItem { // полный аналог loop() из arduino. Нужно помнить, что все модули имеют равный поочередный доступ к центральному loop(), поэтому, необходимо следить // за задержками в алгоритме и не создавать пауз. Кроме того, данная версия перегружает родительскую, поэтому doByInterval() отключается, если // не повторить механизм расчета интервалов. - void loop() { - if (_avgSteps > 1) { - if (_avgCount > _avgSteps) { - // value.valD = _avgSumm / (_avgSteps + 1); - adCresult = _avgSumm / (_avgSteps + 1); - _avgSumm = 0; - _avgCount = 0; - } + void loop() + { + if (_avgSteps > 1) + { - _avgSumm = _avgSumm + IoTgpio.analogRead(_pin); - _avgCount++; + if (millis() > _lastSoundingMillis + _period) + { + realSteps++; + int sounding = IoTgpio.analogRead(_pin); + _adcAverage = _adcAverage + sounding; // IoTgpio.analogRead(_pin); + _lastSoundingMillis = millis(); + //Serial.print("adc= " + String(sounding) + " \t"); + //Serial.println("_adcAverage = " + String(_adcAverage)); + } } IoTItem::loop(); } @@ -76,10 +96,14 @@ class AnalogAdc : public IoTItem { // после замены названия сенсора, на функцию можно не обращать внимания // если сенсор предполагает использование общего объекта библиотеки для нескольких экземпляров сенсора, то в данной функции необходимо предусмотреть // создание и контроль соответствующих глобальных переменных -void* getAPI_AnalogAdc(String subtype, String param) { - if (subtype == F("AnalogAdc")) { +void *getAPI_AnalogAdc(String subtype, String param) +{ + if (subtype == F("AnalogAdc")) + { return new AnalogAdc(param); - } else { + } + else + { return nullptr; } } diff --git a/src/modules/sensors/AnalogAdc/modinfo.json b/src/modules/sensors/AnalogAdc/modinfo.json index ee41ed63..67102aa2 100644 --- a/src/modules/sensors/AnalogAdc/modinfo.json +++ b/src/modules/sensors/AnalogAdc/modinfo.json @@ -42,6 +42,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/sensors/BL0937/BL0937.cpp b/src/modules/sensors/BL0937/BL0937.cpp index 1e709f06..a3eb39d4 100644 --- a/src/modules/sensors/BL0937/BL0937.cpp +++ b/src/modules/sensors/BL0937/BL0937.cpp @@ -138,12 +138,19 @@ class BL0937wh : public IoTItem ~BL0937wh(){}; }; - +#if defined LIBRETINY +void /* ICACHE_RAM_ATTR */ bl0937_cf1_interrupt() +#else void ICACHE_RAM_ATTR bl0937_cf1_interrupt() +#endif { bl0937->cf1_interrupt(); } +#if defined LIBRETINY +void /* ICACHE_RAM_ATTR */ bl0937_cf_interrupt() +#else void ICACHE_RAM_ATTR bl0937_cf_interrupt() +#endif { bl0937->cf_interrupt(); } diff --git a/src/modules/sensors/BL0937/BL0937lib.cpp b/src/modules/sensors/BL0937/BL0937lib.cpp index a54e68b4..d2f4b987 100644 --- a/src/modules/sensors/BL0937/BL0937lib.cpp +++ b/src/modules/sensors/BL0937/BL0937lib.cpp @@ -177,16 +177,21 @@ void BL0937::setResistors(double current, double voltage_upstream, double voltag _calculateDefaultMultipliers(); } } - +#if defined LIBRETINY +void /* ICACHE_RAM_ATTR */ BL0937::cf_interrupt() { +#else void ICACHE_RAM_ATTR BL0937::cf_interrupt() { +#endif unsigned long now = micros(); _power_pulse_width = now - _last_cf_interrupt; _last_cf_interrupt = now; _pulse_count++; } - +#if defined LIBRETINY +void /* ICACHE_RAM_ATTR */ BL0937::cf1_interrupt() { +#else void ICACHE_RAM_ATTR BL0937::cf1_interrupt() { - +#endif unsigned long now = micros(); if ((now - _first_cf1_interrupt) > _pulse_timeout) { diff --git a/src/modules/sensors/BL0937/modinfo.json b/src/modules/sensors/BL0937/modinfo.json index b458c34b..66aeba0f 100644 --- a/src/modules/sensors/BL0937/modinfo.json +++ b/src/modules/sensors/BL0937/modinfo.json @@ -129,9 +129,10 @@ } ] }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/sensors/BL0942/BL0942.cpp b/src/modules/sensors/BL0942/BL0942.cpp new file mode 100644 index 00000000..ec43d1ea --- /dev/null +++ b/src/modules/sensors/BL0942/BL0942.cpp @@ -0,0 +1,291 @@ + +#include "Global.h" +#include "classes/IoTUart.h" +#include "datatypes.h" + +//namespace bl0942 +//{ + class BL0942cmd; + BL0942cmd *BL0942 = nullptr; + DataPacket buffer; + uint8_t inpos = 0xFF; + uint8_t checksum; + uint8_t pubPhase = 0xFF; + bool needUpdate = false; + + static const float BL0942_PREF = 596; // taken from tasmota + static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218 + static const float BL0942_IREF = 251213.46469622; // 305978/1.218 + static const float BL0942_EREF = 3304.61127328; // Measured + + static const char *const TAG = "bl0942"; + + static const uint8_t BL0942_READ_COMMAND = 0x58; + static const uint8_t BL0942_FULL_PACKET = 0xAA; + static const uint8_t BL0942_PACKET_HEADER = 0x55; + + static const uint8_t BL0942_WRITE_COMMAND = 0xA8; + static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10; + static const uint8_t BL0942_REG_MODE = 0x18; + static const uint8_t BL0942_REG_SOFT_RESET = 0x19; + static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; + static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; + + // TODO: Confirm insialisation works as intended + const uint8_t BL0942_INIT[5][6] = { + // Reset to default + {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, + // Enable User Operation Write + {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, + // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS + {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, + // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS + {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, + // 0x181C = Half cycle, Fast RMS threshold 6172 + {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; + + + class BL0942cmd : public IoTUart + { + private: + float i_rms, watt, v_rms, frequency, total_energy_consumption = 0; + // Divide by this to turn into Watt + float power_reference_ = BL0942_PREF; + // Divide by this to turn into Volt + float voltage_reference_ = BL0942_UREF; + // Divide by this to turn into Ampere + float current_reference_ = BL0942_IREF; + // Divide by this to turn into kWh + float energy_reference_ = BL0942_EREF; + + public: + BL0942cmd(String parameters) : IoTUart(parameters) + { + /* + jsonRead(parameters, "R_current", CURRENT_RESISTOR); + jsonRead(parameters, "R_upstream", VOLTAGE_RESISTOR_UPSTREAM); + jsonRead(parameters, "R_downstream", VOLTAGE_RESISTOR_DOWNSTREAM); + jsonRead(parameters, "CF_GPIO", BL0942_CF_GPIO); + jsonRead(parameters, "CF1_GPIO", BL0942_CF1_GPIO); + jsonRead(parameters, "SEL_GPIO", BL0942_SEL_GPIO_INV); + jsonRead(parameters, "kfV", _kfV); + jsonRead(parameters, "kfA", _kfA); + jsonRead(parameters, "kfW", _kfW); + */ + for (auto *i : BL0942_INIT) + { + _myUART->write(i, 6); + delay(1); + } + _myUART->flush(); + BL0942 = this; + } + + void loop() + { + if (_myUART->available()) + { + while (_myUART->available()) + { + uint8_t in; + _myUART->readBytes(&in, 1); + if (inpos < sizeof(buffer) - 1) + { // читаем тело пакета + ((uint8_t *)(&buffer))[inpos] = in; + inpos++; + checksum += in; + } + else if (inpos < sizeof(buffer)) + { // получили контрольную сумму + inpos++; + checksum ^= 0xFF; + if (in != checksum) + { + //ESP_LOGE(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, in); + SerialPrint("E", "BL0942cmd", "Invalid checksum!", _id); + } + else + { + pubPhase = 0; + } + } + else + { + if (in == BL0942_PACKET_HEADER) + { // стартовый хидер + ((uint8_t *)(&buffer))[0] = BL0942_PACKET_HEADER; + inpos = 1; // начало сохранения буфера + checksum = BL0942_READ_COMMAND + BL0942_PACKET_HEADER; // начальные данные рассчета кс + pubPhase = 3; + } + else + { + //ESP_LOGE(TAG, "Invalid data. Header mismatch: %d", in); + SerialPrint("E", "BL0942cmd", "Invalid data. Header mismatch", _id); + } + } + } + } + else if (pubPhase < 3) + { + if (pubPhase == 0) + { + + i_rms = (uint24_t)buffer.i_rms / current_reference_; + + watt = (int24_t)buffer.watt / power_reference_; + + pubPhase = 1; + } + else if (pubPhase == 1) + { + + v_rms = (uint24_t)buffer.v_rms / voltage_reference_; + + frequency = 1000000.0f / buffer.frequency; + + pubPhase = 2; + } + else if (pubPhase == 2) + { + + uint32_t cf_cnt = (uint24_t)buffer.cf_cnt; + total_energy_consumption = cf_cnt / energy_reference_; + + pubPhase = 3; + } + } + IoTItem::loop(); + } + void doByInterval() + { + _myUART->write(BL0942_READ_COMMAND); + _myUART->write(BL0942_FULL_PACKET); + } + float getEnergy() { return total_energy_consumption; } + float getPower() { return watt; } + float getCurrent() { return i_rms; } + float getVoltage() { return v_rms; } + + ~BL0942cmd(){ + + }; + }; + + class BL0942v : public IoTItem + { + private: + public: + BL0942v(String parameters) : IoTItem(parameters) + { + } + + void doByInterval() + { + if (BL0942) + regEvent(BL0942->getVoltage(), "BL0942 V"); + else + { + regEvent(NAN, "BL0942v"); + SerialPrint("E", "BL0942cmd", "initialization error", _id); + } + } + + ~BL0942v(){}; + }; + + class BL0942a : public IoTItem + { + private: + public: + BL0942a(String parameters) : IoTItem(parameters) + { + } + + void doByInterval() + { + if (BL0942) + regEvent(BL0942->getCurrent(), "BL0942 A"); + else + { + regEvent(NAN, "BL0942a"); + SerialPrint("E", "BL0942cmd", "initialization error", _id); + } + } + + ~BL0942a(){}; + }; + + class BL0942w : public IoTItem + { + private: + public: + BL0942w(String parameters) : IoTItem(parameters) + { + } + + void doByInterval() + { + if (BL0942) + regEvent(BL0942->getPower(), "BL0942 W"); + else + { + regEvent(NAN, "BL0942w"); + SerialPrint("E", "BL0942cmd", "initialization error", _id); + } + } + + ~BL0942w(){}; + }; + + class BL0942wh : public IoTItem + { + private: + public: + BL0942wh(String parameters) : IoTItem(parameters) + { + } + + void doByInterval() + { + if (BL0942) + regEvent(BL0942->getEnergy() / 3600.0 / 1000.0, "BL0942 Wh"); + else + { + regEvent(NAN, "BL0942wh"); + SerialPrint("E", "BL0942cmd", "initialization error", _id); + } + } + + ~BL0942wh(){}; + }; + +//} // namespace bl0942 + +void *getAPI_BL0942(String subtype, String param) +{ + if (subtype == F("BL0942v")) + { + return new BL0942v(param); + } + else if (subtype == F("BL0942a")) + { + return new BL0942a(param); + } + else if (subtype == F("BL0942w")) + { + return new BL0942w(param); + } + else if (subtype == F("BL0942wh")) + { + return new BL0942wh(param); + } + else if (subtype == F("BL0942cmd")) + { + return new BL0942cmd(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/sensors/BL0942/datatypes.h b/src/modules/sensors/BL0942/datatypes.h new file mode 100644 index 00000000..4fe9b8fd --- /dev/null +++ b/src/modules/sensors/BL0942/datatypes.h @@ -0,0 +1,116 @@ +#pragma once + +#include + +//#include "helpers.h" + + +namespace internal { + +// Various functions can be constexpr in C++14, but not in C++11 (because their body isn't just a return statement). +// Define a substitute constexpr keyword for those functions, until we can drop C++11 support. +#if __cplusplus >= 201402L +#define constexpr14 constexpr +#else +#define constexpr14 inline // constexpr implies inline +#endif + + +// std::byteswap from C++23 +template constexpr14 T byteswap(T n) { + T m; + for (size_t i = 0; i < sizeof(T); i++) + reinterpret_cast(&m)[i] = reinterpret_cast(&n)[sizeof(T) - 1 - i]; + return m; +} +template<> constexpr14 uint8_t byteswap(uint8_t n) { return n; } +template<> constexpr14 uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } +template<> constexpr14 uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } +template<> constexpr14 uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } +template<> constexpr14 int8_t byteswap(int8_t n) { return n; } +template<> constexpr14 int16_t byteswap(int16_t n) { return __builtin_bswap16(n); } +template<> constexpr14 int32_t byteswap(int32_t n) { return __builtin_bswap32(n); } +template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n); } + +/// Convert a value between host byte order and big endian (most significant byte first) order. +template constexpr14 T convert_big_endian(T val) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return byteswap(val); +#else + return val; +#endif +} + +/// Convert a value between host byte order and little endian (least significant byte first) order. +template constexpr14 T convert_little_endian(T val) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return val; +#else + return byteswap(val); +#endif +} + +/// Wrapper class for memory using big endian data layout, transparently converting it to native order. +template class BigEndianLayout { + public: + constexpr14 operator T() { return convert_big_endian(val_); } + + private: + T val_; +} __attribute__((packed)); + +/// Wrapper class for memory using big endian data layout, transparently converting it to native order. +template class LittleEndianLayout { + public: + constexpr14 operator T() { return convert_little_endian(val_); } + + private: + T val_; +} __attribute__((packed)); + +} // namespace internal + +/// 24-bit unsigned integer type, transparently converting to 32-bit. +struct uint24_t { // NOLINT(readability-identifier-naming) + operator uint32_t() { return val; } + uint32_t val : 24; +} __attribute__((packed)); + +/// 24-bit signed integer type, transparently converting to 32-bit. +struct int24_t { // NOLINT(readability-identifier-naming) + operator int32_t() { return val; } + int32_t val : 24; +} __attribute__((packed)); + +// Integer types in big or little endian data layout. +using uint64_be_t = internal::BigEndianLayout; +using uint32_be_t = internal::BigEndianLayout; +using uint24_be_t = internal::BigEndianLayout; +using uint16_be_t = internal::BigEndianLayout; +using int64_be_t = internal::BigEndianLayout; +using int32_be_t = internal::BigEndianLayout; +using int24_be_t = internal::BigEndianLayout; +using int16_be_t = internal::BigEndianLayout; +using uint64_le_t = internal::LittleEndianLayout; +using uint32_le_t = internal::LittleEndianLayout; +using uint24_le_t = internal::LittleEndianLayout; +using uint16_le_t = internal::LittleEndianLayout; +using int64_le_t = internal::LittleEndianLayout; +using int32_le_t = internal::LittleEndianLayout; +using int24_le_t = internal::LittleEndianLayout; +using int16_le_t = internal::LittleEndianLayout; + +struct DataPacket { + uint8_t frame_header; + uint24_le_t i_rms; + uint24_le_t v_rms; + uint24_le_t i_fast_rms; + int24_le_t watt; + uint24_le_t cf_cnt; + uint16_le_t frequency; + uint8_t reserved1; + uint8_t status; + uint8_t reserved2; + uint8_t reserved3; + uint8_t checksum; +} __attribute__((packed)); \ No newline at end of file diff --git a/src/modules/sensors/BL0942/modinfo.json b/src/modules/sensors/BL0942/modinfo.json new file mode 100644 index 00000000..39ac37b8 --- /dev/null +++ b/src/modules/sensors/BL0942/modinfo.json @@ -0,0 +1,108 @@ +{ + "menuSection": "sensors", + "configItem": [ + { + "global": 0, + "name": "BL0942 Напряжение", + "type": "Reading", + "subtype": "BL0942v", + "id": "bl_v", + "widget": "anydataVlt", + "page": "BL0942", + "descr": "Напряжение", + "int": 15, + "round": 1 + }, + { + "global": 0, + "name": "BL0942 Сила тока", + "type": "Reading", + "subtype": "BL0942a", + "id": "bl_a", + "widget": "anydataAmp", + "page": "BL0942", + "descr": "Сила тока", + "int": 15, + "round": 1 + }, + { + "global": 0, + "name": "BL0942 Мощность", + "type": "Reading", + "subtype": "BL0942w", + "id": "bl_w", + "widget": "anydataWt", + "page": "BL0942", + "descr": "Мощность", + "int": 15, + "round": 1 + }, + { + "global": 0, + "name": "BL0942 Энергия", + "type": "Reading", + "subtype": "BL0942wh", + "id": "bl_wh", + "widget": "anydataWth", + "page": "BL0942", + "descr": "Энергия", + "int": 15, + "round": 1 + }, + { + "global": 0, + "name": "BL0942 настройка", + "type": "Reading", + "subtype": "BL0942cmd", + "id": "bl_set", + "widget": "nil", + "page": "", + "descr": "", + "int": "5", + "tx": 17, + "rx": 16, + "line": 2, + "speed": 9600 + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "", + "moduleName": "BL0942", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Счетчик электроэнергии BL0942", + "moduleDesc": "Считает потраченную электроэнергию, измеряет напряжение, силу тока и прочие параметры.", + "propInfo": { + "int": "Количество секунд между опросами датчика. В bl_set интервал между попытками калибровки (т.к. нужны сначала данные от датчика)", + "btn-reset": "Энергия BL0942 будет сброшена к нулю.", + "R_current": "Резистор подключенный последовательно к основной линии", + "R_upstream": "это 5 резисторов по 470 Ком в делителе напряжения, который питает вывод V2P", + "R_downstream": "это резистор емкостью 1 Ком в делителе напряжения, который питает вывод V2P", + "CF_GPIO": "пин CF", + "CF1_GPIO": "пин CF1", + "SEL_GPIO": "пин SEL", + "kfV": "Коэффициент корректировки напряжение, указать после калибровки", + "kfA": "Коэффициент корректировки тока, указать после калибровки", + "kfW": "Коэффициент корректировки мощности, указать после калибровки" + }, + "funcInfo": [ + { + "name": "calibration", + "descr": "Расчет коэффициентов калибровки. Вызывать от имени BL0942 настройка. bl_set.calibration(220, 16, 3.5). Полученный коэффициенты искать в логе и ввести в конфигурацию kfV, kfA и kfW", + "params": ["Напряжение, Ток, Мощность"] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [], + "bk72*": [] + } +} \ No newline at end of file diff --git a/src/modules/sensors/Ble/Ble.cpp b/src/modules/sensors/Ble/Ble.cpp index c9713e57..b687bfe8 100644 --- a/src/modules/sensors/Ble/Ble.cpp +++ b/src/modules/sensors/Ble/Ble.cpp @@ -178,7 +178,11 @@ void scanEndedCB(NimBLEScanResults results) // pBLEScan->clearResults(); } -class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) +//class BleScan : public IoTItem, NimBLEScanCallbacks +//#else +class BleScan : public IoTItem, NimBLEScanCallbacks //BLEAdvertisedDeviceCallbacks //NimBLEScanCallbacks +//#endif { private: // описание параметров передаваемых из настроек датчика из веба @@ -201,7 +205,7 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks return spr; } - void onResult(BLEAdvertisedDevice *advertisedDevice) + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { JsonObject BLEdata = doc.to(); String mac_adress_ = advertisedDevice->getAddress().toString().c_str(); @@ -214,11 +218,20 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks } if (advertisedDevice->haveManufacturerData()) { +#if defined (esp32c6_4mb) || defined (esp32c6_8mb) char *manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); +#else + std::string manufacturerdata = NimBLEUtils::dataToHexString((uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); +#endif + BLEdata["manufacturerdata"] = manufacturerdata; + #if defined (esp32c6_4mb) || defined (esp32c6_8mb) free(manufacturerdata); + #endif } - if (advertisedDevice->haveRSSI()) +//#if !defined (esp32c6_4mb) && !defined (esp32c6_8mb) //&& !defined (esp32_4mb3f) +// if (advertisedDevice->haveRSSI()) +//#endif BLEdata["rssi"] = (int)advertisedDevice->getRSSI(); if (advertisedDevice->haveTXPower()) BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower(); @@ -243,7 +256,6 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks mac_address = BLEdata["id"].as(); } mac_address.replace(":", ""); - if (_debug < 2) { BLEdata.remove("manufacturerdata"); @@ -292,12 +304,17 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks BleScan(String parameters) : IoTItem(parameters) { _scanDuration = jsonReadInt(parameters, "scanDuration"); + _scanDuration = _scanDuration * 1000; _filter = jsonReadStr(parameters, "filter"); jsonRead(parameters, "debug", _debug); BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); // create new scan - pBLEScan->setAdvertisedDeviceCallbacks(this); +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f) + pBLEScan->setScanCallbacks(this); +//#else +// pBLEScan->setAdvertisedDeviceCallbacks(this); +//#endif pBLEScan->setActiveScan(false); // active scan uses more power, but get results faster pBLEScan->setInterval(100); pBLEScan->setWindow(99); // less or equal setInterval value @@ -312,7 +329,11 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks if (_scanDuration > 0) { SerialPrint("i", F("BLE"), "Start Scanning..."); - pBLEScan->start(_scanDuration, scanEndedCB, false); +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f) + pBLEScan->start(_scanDuration, false); +//#else +// pBLEScan->start(_scanDuration, scanEndedCB, false); +//#endif } } } diff --git a/src/modules/sensors/Ble/modinfo.json b/src/modules/sensors/Ble/modinfo.json index c4cb3106..65e57602 100644 --- a/src/modules/sensors/Ble/modinfo.json +++ b/src/modules/sensors/Ble/modinfo.json @@ -64,8 +64,14 @@ }, "defActive": false, "usedLibs": { + "esp32c6_4mb": [ + "https://github.com/Mit4el/NimBLE-Arduino.git#c6-build" + ], + "esp32c6_8mb": [ + "https://github.com/Mit4el/NimBLE-Arduino.git#c6-build" + ], "esp32*": [ - "https://github.com/Mit4el/NimBLE-Arduino.git" + "https://github.com/Mit4el/NimBLE-Arduino.git#release/2.2" ], "esp32s2_4mb": [ "exclude" diff --git a/src/modules/sensors/Ble_part1/Ble_p1.cpp b/src/modules/sensors/Ble_part1/Ble_p1.cpp index 7ead4a2c..c174a274 100644 --- a/src/modules/sensors/Ble_part1/Ble_p1.cpp +++ b/src/modules/sensors/Ble_part1/Ble_p1.cpp @@ -178,7 +178,11 @@ void scanEndedCB(NimBLEScanResults results) // pBLEScan->clearResults(); } -class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) +//class BleScan : public IoTItem, NimBLEScanCallbacks +//#else +class BleScan : public IoTItem, NimBLEScanCallbacks//BLEAdvertisedDeviceCallbacks //NimBLEScanCallbacks +//#endif { private: // описание параметров передаваемых из настроек датчика из веба @@ -201,24 +205,33 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks return spr; } - void onResult(BLEAdvertisedDevice *advertisedDevice) + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { JsonObject BLEdata = doc.to(); String mac_adress_ = advertisedDevice->getAddress().toString().c_str(); mac_adress_.toUpperCase(); BLEdata["id"] = (char *)mac_adress_.c_str(); - + //SerialPrint("i", F("BLE"), "FOUND "+ mac_adress_); if (advertisedDevice->haveName()) { BLEdata["name"] = (char *)advertisedDevice->getName().c_str(); } if (advertisedDevice->haveManufacturerData()) { +#if defined (esp32c6_4mb) || defined (esp32c6_8mb) char *manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); +#else + std::string manufacturerdata = NimBLEUtils::dataToHexString((uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); +#endif + BLEdata["manufacturerdata"] = manufacturerdata; + #if defined (esp32c6_4mb) || defined (esp32c6_8mb) free(manufacturerdata); + #endif } - if (advertisedDevice->haveRSSI()) +//#if !defined (esp32c6_4mb) && !defined (esp32c6_8mb) //&& !defined (esp32_4mb3f) +// if (advertisedDevice->haveRSSI()) +//#endif BLEdata["rssi"] = (int)advertisedDevice->getRSSI(); if (advertisedDevice->haveTXPower()) BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower(); @@ -291,12 +304,17 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks BleScan(String parameters) : IoTItem(parameters) { _scanDuration = jsonReadInt(parameters, "scanDuration"); + _scanDuration = _scanDuration * 1000; _filter = jsonReadStr(parameters, "filter"); jsonRead(parameters, "debug", _debug); BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); // create new scan - pBLEScan->setAdvertisedDeviceCallbacks(this); +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f) + pBLEScan->setScanCallbacks(this); +//#else +// pBLEScan->setAdvertisedDeviceCallbacks(this); +//#endif pBLEScan->setActiveScan(false); // active scan uses more power, but get results faster pBLEScan->setInterval(100); pBLEScan->setWindow(99); // less or equal setInterval value @@ -311,7 +329,11 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks if (_scanDuration > 0) { SerialPrint("i", F("BLE"), "Start Scanning..."); - pBLEScan->start(_scanDuration, scanEndedCB, false); +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f) + pBLEScan->start(_scanDuration, false); +//#else +// pBLEScan->start(_scanDuration, scanEndedCB, false); +//#endif } } } diff --git a/src/modules/sensors/Ble_part1/modinfo.json b/src/modules/sensors/Ble_part1/modinfo.json index 199c92b8..2ceaa1a0 100644 --- a/src/modules/sensors/Ble_part1/modinfo.json +++ b/src/modules/sensors/Ble_part1/modinfo.json @@ -64,8 +64,14 @@ }, "defActive": false, "usedLibs": { + "esp32c6_4mb": [ + "https://github.com/Mit4el/NimBLE-Arduino.git#c6-build" + ], + "esp32c6_8mb": [ + "https://github.com/Mit4el/NimBLE-Arduino.git#c6-build" + ], "esp32*": [ - "https://github.com/Mit4el/NimBLE-Arduino.git" + "https://github.com/Mit4el/NimBLE-Arduino.git#release/2.2" ], "esp32s2_4mb": [ "exclude" diff --git a/src/modules/sensors/Ble_part2/Ble_p2.cpp b/src/modules/sensors/Ble_part2/Ble_p2.cpp index 0c8de171..bb815f0c 100644 --- a/src/modules/sensors/Ble_part2/Ble_p2.cpp +++ b/src/modules/sensors/Ble_part2/Ble_p2.cpp @@ -178,7 +178,11 @@ void scanEndedCB(NimBLEScanResults results) // pBLEScan->clearResults(); } -class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) +//class BleScan : public IoTItem, NimBLEScanCallbacks +//#else +class BleScan : public IoTItem, NimBLEScanCallbacks //BLEAdvertisedDeviceCallbacks //NimBLEScanCallbacks +//#endif { private: // описание параметров передаваемых из настроек датчика из веба @@ -201,7 +205,7 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks return spr; } - void onResult(BLEAdvertisedDevice *advertisedDevice) + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { JsonObject BLEdata = doc.to(); String mac_adress_ = advertisedDevice->getAddress().toString().c_str(); @@ -214,11 +218,20 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks } if (advertisedDevice->haveManufacturerData()) { +#if defined (esp32c6_4mb) || defined (esp32c6_8mb) char *manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); +#else + std::string manufacturerdata = NimBLEUtils::dataToHexString((uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); +#endif + BLEdata["manufacturerdata"] = manufacturerdata; + #if defined (esp32c6_4mb) || defined (esp32c6_8mb) free(manufacturerdata); + #endif } - if (advertisedDevice->haveRSSI()) +//#if !defined (esp32c6_4mb) && !defined (esp32c6_8mb) //&& !defined (esp32_4mb3f) +// if (advertisedDevice->haveRSSI()) +//#endif BLEdata["rssi"] = (int)advertisedDevice->getRSSI(); if (advertisedDevice->haveTXPower()) BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower(); @@ -254,7 +267,6 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks BLEdata.remove("track"); BLEdata.remove("id"); } - // дописываем время прихода пакета данных BLEdata["last"] = millis(); if (_debug) @@ -292,12 +304,17 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks BleScan(String parameters) : IoTItem(parameters) { _scanDuration = jsonReadInt(parameters, "scanDuration"); + _scanDuration = _scanDuration * 1000; _filter = jsonReadStr(parameters, "filter"); jsonRead(parameters, "debug", _debug); BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); // create new scan - pBLEScan->setAdvertisedDeviceCallbacks(this); +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f) + pBLEScan->setScanCallbacks(this); +//#else +// pBLEScan->setAdvertisedDeviceCallbacks(this); +//#endif pBLEScan->setActiveScan(false); // active scan uses more power, but get results faster pBLEScan->setInterval(100); pBLEScan->setWindow(99); // less or equal setInterval value @@ -312,7 +329,11 @@ class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks if (_scanDuration > 0) { SerialPrint("i", F("BLE"), "Start Scanning..."); - pBLEScan->start(_scanDuration, scanEndedCB, false); +//#if defined (esp32c6_4mb) || defined (esp32c6_8mb) //|| defined (esp32_4mb3f) + pBLEScan->start(_scanDuration, false); +//#else +// pBLEScan->start(_scanDuration, scanEndedCB, false); +//#endif } } } diff --git a/src/modules/sensors/Ble_part2/modinfo.json b/src/modules/sensors/Ble_part2/modinfo.json index b32ccd52..06f8fbf9 100644 --- a/src/modules/sensors/Ble_part2/modinfo.json +++ b/src/modules/sensors/Ble_part2/modinfo.json @@ -64,8 +64,14 @@ }, "defActive": false, "usedLibs": { + "esp32c6_4mb": [ + "https://github.com/Mit4el/NimBLE-Arduino.git#c6-build" + ], + "esp32c6_8mb": [ + "https://github.com/Mit4el/NimBLE-Arduino.git#c6-build" + ], "esp32*": [ - "https://github.com/Mit4el/NimBLE-Arduino.git" + "https://github.com/Mit4el/NimBLE-Arduino.git#release/2.2" ], "esp32s2_4mb": [ "exclude" diff --git a/src/modules/sensors/Ds18b20/modinfo.json b/src/modules/sensors/Ds18b20/modinfo.json index 85bd3c96..77176e4e 100644 --- a/src/modules/sensors/Ds18b20/modinfo.json +++ b/src/modules/sensors/Ds18b20/modinfo.json @@ -40,10 +40,16 @@ "defActive": true, "usedLibs": { "esp32*": [ - "https://github.com/milesburton/Arduino-Temperature-Control-Library" + "milesburton/DallasTemperature @ ^4.0.4" ], + "esp32c6_4mb": [ + "https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg" + ], + "esp32c6_8mb": [ + "https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg" + ], "esp82*": [ - "https://github.com/milesburton/Arduino-Temperature-Control-Library" + "milesburton/DallasTemperature @ ^4.0.4" ] } } \ No newline at end of file diff --git a/src/modules/sensors/DscKeybus/DscKeybus.cpp b/src/modules/sensors/DscKeybus/DscKeybus.cpp new file mode 100644 index 00000000..43140826 --- /dev/null +++ b/src/modules/sensors/DscKeybus/DscKeybus.cpp @@ -0,0 +1,450 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "utils/SerialPrint.h" + +#include + +#include + +struct DscKeybusSharedContext { + dscKeybusInterface* iface = nullptr; + int clockPin = -1; + int readPin = -1; + int writePin = -1; +} dscKeybusShared; + +static dscKeybusInterface* acquireDscInterface(int clockPin, + int readPin, + int writePin, + bool virtualKeypad, + bool processModule, + bool hideDigits) { + const byte normalizedWrite = writePin < 0 ? 255 : static_cast(writePin); + + if (!dscKeybusShared.iface) { + dscKeybusShared.iface = new dscKeybusInterface(static_cast(clockPin), + static_cast(readPin), + normalizedWrite); + dscKeybusShared.clockPin = clockPin; + dscKeybusShared.readPin = readPin; + dscKeybusShared.writePin = writePin; + + dscKeybusShared.iface->hideKeypadDigits = hideDigits; + dscKeybusInterface::virtualKeypad = virtualKeypad; + dscKeybusInterface::processModuleData = processModule; + + dscKeybusShared.iface->begin(Serial); + dscKeybusShared.iface->resetStatus(); + SerialPrint("i", F("Sensor DscKeybus"), "interface ready"); + } else { + if (dscKeybusShared.clockPin != clockPin || dscKeybusShared.readPin != readPin || + dscKeybusShared.writePin != writePin) { + SerialPrint("E", F("Sensor DscKeybus"), "pins mismatch, reuse first init"); + } + + if (virtualKeypad) { + dscKeybusInterface::virtualKeypad = true; + } + if (processModule) { + dscKeybusInterface::processModuleData = true; + } + if (hideDigits) { + dscKeybusShared.iface->hideKeypadDigits = true; + } + } + + return dscKeybusShared.iface; +} + +class DscKeybusItem : public IoTItem { +public: + DscKeybusItem(String parameters, const String& subtype); + + void doByInterval() override; + void loop() override; + IoTValue execute(String command, std::vector& param) override; + +private: + dscKeybusInterface* iface(); + String makeStatus() const; + String makeZones() const; + String makeTrouble() const; + String makeRaw() const; + void publishIfChanged(const String& payload); + int readPinFromConfig(const String& jsonValue, int fallback) const; + byte clampPartition(int candidate) const; + +private: + int _clockPin; + int _readPin; + int _writePin; + byte _partition; + byte _writePartition; + bool _virtualKeypad; + bool _processModule; + bool _hideDigits; + String _mode; + String _lastValue; + bool _initialised = false; +}; + +DscKeybusItem::DscKeybusItem(String parameters, const String& subtype) : IoTItem(parameters) { + _clockPin = readPinFromConfig(jsonReadStr(parameters, "clockPin"), 18); + _readPin = readPinFromConfig(jsonReadStr(parameters, "readPin"), 19); + _writePin = readPinFromConfig(jsonReadStr(parameters, "writePin"), 21); + + _partition = clampPartition(jsonReadInt(parameters, "partition")); + if (_partition == 0) { + _partition = 1; + } + + _writePartition = clampPartition(jsonReadInt(parameters, "writePartition")); + if (_writePartition == 0) { + _writePartition = _partition; + } + + _virtualKeypad = jsonReadBool(parameters, "virtualKeypad"); + _processModule = jsonReadBool(parameters, "processModule"); + _hideDigits = jsonReadBool(parameters, "hideDigits"); + + _mode = jsonReadStr(parameters, "mode"); + if (_mode.length() == 0) { + _mode = subtype; + } + if (_mode.length() == 0) { + _mode = F("status"); + } + _mode.toLowerCase(); + + if (subtype.equalsIgnoreCase(F("DscKeybusZones"))) { + _mode = F("zones"); + } + + if (_writePin < 0) { + _virtualKeypad = false; + } +} + +dscKeybusInterface* DscKeybusItem::iface() { + if (!_initialised) { + dscKeybusInterface* ifacePtr = acquireDscInterface(_clockPin, + _readPin, + _writePin, + _virtualKeypad, + _processModule, + _hideDigits); + if (!ifacePtr) { + return nullptr; + } + dscKeybusInterface::writePartition = _writePartition; + _initialised = true; + } + + return dscKeybusShared.iface; +} + +String DscKeybusItem::makeStatus() const { + if (!dscKeybusShared.iface) { + return F("offline"); + } + + const byte idx = clampPartition(_partition) - 1; + std::vector tokens; + + if (!dscKeybusShared.iface->keybusConnected) { + tokens.emplace_back(F("keybus_offline")); + } else { + if (dscKeybusShared.iface->alarm[idx]) { + tokens.emplace_back(F("alarm")); + } + if (dscKeybusShared.iface->fire[idx]) { + tokens.emplace_back(F("fire")); + } + if (dscKeybusShared.iface->exitDelay[idx]) { + tokens.emplace_back(F("exit_delay")); + } + if (dscKeybusShared.iface->entryDelay[idx]) { + tokens.emplace_back(F("entry_delay")); + } + if (dscKeybusShared.iface->noEntryDelay[idx]) { + tokens.emplace_back(F("no_entry_delay")); + } + if (dscKeybusShared.iface->armed[idx]) { + if (dscKeybusShared.iface->armedAway[idx]) { + tokens.emplace_back(F("armed_away")); + } else if (dscKeybusShared.iface->armedStay[idx]) { + tokens.emplace_back(F("armed_stay")); + } else { + tokens.emplace_back(F("armed")); + } + } else if (dscKeybusShared.iface->ready[idx]) { + tokens.emplace_back(F("ready")); + } else { + tokens.emplace_back(F("not_ready")); + } + if (dscKeybusShared.iface->disabled[idx]) { + tokens.emplace_back(F("disabled")); + } + } + + if (dscKeybusShared.iface->trouble) { + tokens.emplace_back(F("trouble")); + } + if (dscKeybusShared.iface->powerTrouble) { + tokens.emplace_back(F("ac_fail")); + } + if (dscKeybusShared.iface->batteryTrouble) { + tokens.emplace_back(F("battery")); + } + + if (tokens.empty()) { + tokens.emplace_back(F("idle")); + } + + String result; + for (size_t i = 0; i < tokens.size(); ++i) { + if (i > 0) { + result += F(", "); + } + result += tokens[i]; + } + return result; +} + +String DscKeybusItem::makeZones() const { + if (!dscKeybusShared.iface) { + return F("offline"); + } + + std::vector zones; + for (byte group = 0; group < dscZones; ++group) { + byte mask = dscKeybusShared.iface->openZones[group]; + if (mask == 0) { + continue; + } + for (byte bit = 0; bit < 8; ++bit) { + if (mask & (1 << bit)) { + zones.emplace_back(group * 8 + bit + 1); + } + } + } + + if (zones.empty()) { + return F("none"); + } + + String result; + for (size_t i = 0; i < zones.size(); ++i) { + if (i > 0) { + result += F(", "); + } + result += String(zones[i]); + } + return result; +} + +String DscKeybusItem::makeTrouble() const { + if (!dscKeybusShared.iface) { + return F("offline"); + } + + std::vector tokens; + + if (!dscKeybusShared.iface->keybusConnected) { + tokens.emplace_back(F("keybus")); + } + if (dscKeybusShared.iface->trouble) { + tokens.emplace_back(F("panel")); + } + if (dscKeybusShared.iface->powerTrouble) { + tokens.emplace_back(F("ac")); + } + if (dscKeybusShared.iface->batteryTrouble) { + tokens.emplace_back(F("battery")); + } + if (dscKeybusShared.iface->keypadAuxAlarm) { + tokens.emplace_back(F("aux")); + } + if (dscKeybusShared.iface->keypadPanicAlarm) { + tokens.emplace_back(F("panic")); + } + if (dscKeybusShared.iface->keypadFireAlarm) { + tokens.emplace_back(F("fire_keypad")); + } + + if (tokens.empty()) { + return F("ok"); + } + + String result; + for (size_t i = 0; i < tokens.size(); ++i) { + if (i > 0) { + result += F(", "); + } + result += tokens[i]; + } + return result; +} + +String DscKeybusItem::makeRaw() const { + if (!dscKeybusShared.iface) { + return F("offline"); + } + + String result; + result.reserve(dscReadSize * 3); + + const byte size = dscKeybusShared.iface->panelByteCount + 2; + for (byte i = 0; i < size && i < dscReadSize; ++i) { + if (i > 0) { + result += ' '; + } + const byte value = dscKeybusShared.iface->panelData[i]; + if (value < 16) { + result += '0'; + } + result += String(value, HEX); + } + result.toUpperCase(); + + if (result.length() == 0) { + return F("no data"); + } + return result; +} + +void DscKeybusItem::publishIfChanged(const String& payload) { + if (payload == _lastValue) { + return; + } + + value.valS = payload; + regEvent(value.valS, "DscKeybus"); + _lastValue = payload; +} + +void DscKeybusItem::doByInterval() { + if (!iface()) { + return; + } + + if (_mode == F("zones")) { + publishIfChanged(makeZones()); + } else if (_mode == F("trouble")) { + publishIfChanged(makeTrouble()); + } else if (_mode == F("raw")) { + publishIfChanged(makeRaw()); + } else { + publishIfChanged(makeStatus()); + } +} + +void DscKeybusItem::loop() { + dscKeybusInterface* ifacePtr = iface(); + if (ifacePtr) { + if (ifacePtr->loop()) { + if (_mode == F("zones")) { + if (ifacePtr->openZonesStatusChanged || ifacePtr->alarmZonesStatusChanged) { + publishIfChanged(makeZones()); + } + } else if (_mode == F("trouble")) { + if (ifacePtr->troubleChanged || ifacePtr->powerChanged || ifacePtr->batteryChanged || + ifacePtr->keypadAuxAlarm || ifacePtr->keypadFireAlarm || ifacePtr->keypadPanicAlarm || + ifacePtr->keybusChanged) { + publishIfChanged(makeTrouble()); + } + } else if (_mode == F("raw")) { + publishIfChanged(makeRaw()); + } else { + if (ifacePtr->statusChanged || ifacePtr->keybusChanged) { + publishIfChanged(makeStatus()); + } + } + } + } + + IoTItem::loop(); +} + +IoTValue DscKeybusItem::execute(String command, std::vector& param) { + IoTValue ret; + dscKeybusInterface* ifacePtr = iface(); + if (!ifacePtr) { + return ret; + } + + command.toLowerCase(); + + if (command == F("write")) { + if (!_virtualKeypad || _writePin < 0) { + SerialPrint("E", F("Sensor DscKeybus"), "virtual keypad disabled"); + return ret; + } + if (param.empty()) { + return ret; + } + + String payload = param[0].valS; + if (payload.length() == 0) { + payload = String(param[0].valD); + } + payload.trim(); + if (payload.length() == 0) { + return ret; + } + + dscKeybusInterface::writePartition = _writePartition; + ifacePtr->write(payload.c_str(), true); + SerialPrint("i", F("Sensor DscKeybus"), "keys sent"); + return ret; + } + + if (command == F("resetstatus")) { + ifacePtr->resetStatus(); + publishIfChanged(makeStatus()); + return ret; + } + + if (command == F("setpartition") && !param.empty()) { + int value = param[0].valS.length() ? param[0].valS.toInt() : static_cast(param[0].valD); + byte candidate = clampPartition(value); + if (candidate >= 1) { + _partition = candidate; + publishIfChanged(makeStatus()); + } + return ret; + } + + if (command == F("setwritepartition") && !param.empty()) { + int value = param[0].valS.length() ? param[0].valS.toInt() : static_cast(param[0].valD); + byte candidate = clampPartition(value); + if (candidate >= 1) { + _writePartition = candidate; + dscKeybusInterface::writePartition = _writePartition; + } + return ret; + } + + return ret; +} + +int DscKeybusItem::readPinFromConfig(const String& jsonValue, int fallback) const { + if (jsonValue.length() == 0 || jsonValue.equalsIgnoreCase(F("null"))) { + return fallback; + } + return jsonValue.toInt(); +} + +byte DscKeybusItem::clampPartition(int candidate) const { + if (candidate < 1) { + return 0; + } + if (candidate > dscPartitions) { + return dscPartitions; + } + return static_cast(candidate); +} + +void* getAPI_DscKeybus(String subtype, String param) { + return new DscKeybusItem(param, subtype); +} diff --git a/src/modules/sensors/DscKeybus/modinfo.json b/src/modules/sensors/DscKeybus/modinfo.json new file mode 100644 index 00000000..4757350b --- /dev/null +++ b/src/modules/sensors/DscKeybus/modinfo.json @@ -0,0 +1,78 @@ +{ + "menuSection": "sensors", + "configItem": [ + { + "global": 0, + "name": "DSC Keybus Статус", + "type": "Reading", + "subtype": "DscKeybus", + "id": "dscState", + "widget": "anydataDef", + "page": "Сенсоры", + "descr": "Текущее состояние выбранного раздела системы DSC", + "int": 1, + "clockPin": "18", + "readPin": "19", + "writePin": "21", + "partition": 1, + "writePartition": 1, + "mode": "status", + "virtualKeypad": false, + "processModule": false, + "hideDigits": true + }, + { + "global": 0, + "name": "DSC Keybus Зоны", + "type": "Reading", + "subtype": "DscKeybus", + "id": "dscZones", + "widget": "anydataDef", + "page": "Сенсоры", + "descr": "Перечень открытых зон панели DSC", + "int": 3, + "clockPin": "18", + "readPin": "19", + "writePin": "-1", + "partition": 1, + "mode": "zones", + "virtualKeypad": false, + "processModule": false, + "hideDigits": true + } + ], + "about": { + "authorName": "IoTManager Team", + "authorContact": "https://iotmanager.org", + "authorGit": "https://github.com/IoTManagerProject", + "specialThanks": "https://github.com/taligentx/dscKeybusInterface", + "moduleName": "DscKeybus", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 45, + "esp8266_4mb": 45 + }, + "title": "Интерфейс DSC Keybus", + "moduleDesc": "Интеграция с библиотекой dscKeybusInterface для опроса состояния панели DSC, отслеживания зон и отправки команд виртуальной клавиатурой.", + "propInfo": { + "clockPin": "GPIO, подключённый к линии CLOCK шины Keybus.", + "readPin": "GPIO, подключённый к линии DATA (чтение).", + "writePin": "GPIO для записи в шину (виртуальная клавиатура). Значение -1 отключает запись.", + "partition": "Номер раздела панели DSC (1-8).", + "writePartition": "Номер раздела, в который отправляются команды виртуальной клавиатуры.", + "mode": "Формат вывода: status, zones, trouble, raw.", + "virtualKeypad": "Включает виртуальную клавиатуру и возможность отправки команд.", + "processModule": "Обрабатывать сообщения модулей/клавиатуры (при необходимости).", + "hideDigits": "Скрывать цифры нажатых клавиш при ведении логов." + } + }, + "defActive": false, + "usedLibs": { + "esp32*": [ + "https://github.com/taligentx/dscKeybusInterface.git" + ], + "esp82*": [ + "https://github.com/taligentx/dscKeybusInterface.git" + ] + } +} diff --git a/src/modules/sensors/EnergyMon485/modinfo.json b/src/modules/sensors/EnergyMon485/modinfo.json index 233bfabc..1e5823a0 100644 --- a/src/modules/sensors/EnergyMon485/modinfo.json +++ b/src/modules/sensors/EnergyMon485/modinfo.json @@ -67,17 +67,6 @@ "defActive": false, "usedLibs": { "esp32*": [], - "esp82*": [], - "esp32_4mb": [], - "esp32_4mb3f": [], - "esp32cam_4mb": [], - "esp32c3m_4mb": [], - "esp8266_4mb": [], - "esp8266_1mb": [], - "esp8266_1mb_ota": [], - "esp8285_1mb": [], - "esp8285_1mb_ota": [], - "esp8266_2mb": [], - "esp8266_2mb_ota": [] + "esp82*": [] } } \ No newline at end of file diff --git a/src/modules/sensors/ExternalMQTT/ExternalMQTT.cpp b/src/modules/sensors/ExternalMQTT/ExternalMQTT.cpp index 744381b6..72eff08b 100644 --- a/src/modules/sensors/ExternalMQTT/ExternalMQTT.cpp +++ b/src/modules/sensors/ExternalMQTT/ExternalMQTT.cpp @@ -75,7 +75,8 @@ class ExternalMQTT : public IoTItem for (JsonPair kv : jsonObject) { String key = kv.key().c_str(); - String val = kv.value(); + String val = kv.value().as(); + val.trim(); if (_debug) { SerialPrint("i", "ExternalMQTT", "Received MAC: " + dev + " key=" + key + " val=" + val); diff --git a/src/modules/sensors/MQgas/modinfo.json b/src/modules/sensors/MQgas/modinfo.json index bcaa1fd6..4b1f42d9 100644 --- a/src/modules/sensors/MQgas/modinfo.json +++ b/src/modules/sensors/MQgas/modinfo.json @@ -252,20 +252,9 @@ ] }, - "defActive": true, + "defActive": false, "usedLibs": { - "esp32_4mb": [], - "esp32_4mb3f": [], - "esp32cam_4mb": [], - "esp32_16mb": [], - "esp32s2_4mb": [], - "esp8266_4mb": [], - "esp8266_16mb": [], - "esp8266_1mb": [], - "esp8266_1mb_ota": [], - "esp8285_1mb": [], - "esp8285_1mb_ota": [], - "esp8266_2mb": [], - "esp8266_2mb_ota": [] + "esp32*": [], + "esp82*": [] } } diff --git a/src/modules/sensors/ModbusRTU/MudbusRTU.cpp b/src/modules/sensors/ModbusRTU/MudbusRTU.cpp new file mode 100644 index 00000000..2c4d9489 --- /dev/null +++ b/src/modules/sensors/ModbusRTU/MudbusRTU.cpp @@ -0,0 +1,399 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include +#include + +// https://github.com/4-20ma/ModbusMaster + +#include + +// class ModbusUart; +Stream *_modbusUART = nullptr; +int _DIR_PIN = 0; + +#define UART_LINE 2 + +// Modbus stuff +// Данные Modbus по умолчанию + +#define MODBUS_RX_PIN 18 // Rx pin +#define MODBUS_TX_PIN 19 // Tx pin +#define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication + +void modbusPreTransmission() +{ + // delay(500); + if (_DIR_PIN) + digitalWrite(_DIR_PIN, HIGH); +} + +// Pin 4 made low for Modbus receive mode +// Контакт 4 установлен на низком уровне для режима приема Modbus +void modbusPostTransmission() +{ + if (_DIR_PIN) + digitalWrite(_DIR_PIN, LOW); + // delay(500); +} + +float readFunctionModBus(const uint8_t &func, const uint8_t &addr, const uint16_t ®, const uint8_t &count = 1, const bool &isFloat = 0) +{ + float retValue = 0; + if (_modbusUART) + { + + node.begin(addr, (Stream &)*_modbusUART); + uint8_t result; + // uint16_t data[2] = {0, 0}; + uint32_t reading; + switch (func) + { + // Modbus function 0x01 Read Coils + // Функция Modbus 0x01 Чтение Катушек + case 1: // 0x01 + count = count > 16 ? 16 : count; + count = count < 1 ? 1 : count; + result = node.readCoils(reg, count); + if (_debug) + { + SerialPrint("I", "ModbusNode", "readCoils, addr: " + String(addr, HEX) + ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX)); + } + break; + + // Modbus function 0x02 Read Discrete Inputs + // Функция Modbus 0x02 Чтение дискретных входов + case 2: // 0x02 + count = count > 16 ? 16 : count; + count = count < 1 ? 1 : count; + result = node.readDiscreteInputs(reg, count); + if (_debug) + { + SerialPrint("I", "ModbusNode", "readDiscreteInputs, addr: " + String(addr, HEX) + ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX)); + } + break; + + // Modbus function 0x03 Read Holding Registers + case 3: // 0x03 + count = count > 2 ? 2 : count; + count = count < 1 ? 1 : count; + result = node.readHoldingRegisters(reg, count); + if (_debug) + { + SerialPrint("I", "ModbusNode", "readHoldingRegisters, addr: " + String(addr, HEX) + ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX)); + } + break; + + // Modbus function 0x04 Read Input Registers + // Функция Modbus 0x04 Чтение входных регистров + case 4: // 0x04 + count = count > 2 ? 2 : count; + count = count < 1 ? 1 : count; + result = node.readInputRegisters(reg, count); + if (_debug) + { + SerialPrint("I", "ModbusNode", "readInputRegisters, addr: " + String(addr, HEX) ", reg: " + String(reg, HEX) + " = result: " + String(result, HEX)); + } + break; + + default: + SerialPrint("I", "ModbusNode", "Unknown function or not supported: " + String(func, HEX)); + + break; + } + + if (result == node.ku8MBSuccess) + { + if ((func == 3 || func == 4) && countR == 2) + { + reading = node.getResponseBuffer(0x00) | + node.getResponseBuffer(0x01) << 16; + } + else + { + reading = node.getResponseBuffer(0x00); + } + node.clearResponseBuffer(); + + if (_debug) + { + SerialPrint("I", "ModbusMaster", "Success, Received data, register: " + String(reg) + " = " + String(reading, HEX)); + } + if (isFloat) + { + retValue = *(float *)&reading; + } + retValue = reading; + } + else + { + + if (_debug) + { + SerialPrint("I", "ModbusMaster", "Failed, Response Code: " + String(result, HEX)); + } + } + } + return retValue; +} + +ModbusMaster node; + +class ModbusNode : public IoTItem +{ +private: + // Initialize the ModbusMaster object as node + // Инициализируем объект ModbusMaster как узел + + uint8_t _addr = 0; // Адрес слейва от 1 до 247 + String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????) + String _funcStr = ""; // Функция ModBUS + uint8_t _func; + uint16_t _reg = 0; + uint8_t _countReg = 1; + bool _isFloat = 0; + bool _debug; // Дебаг + +public: + ModbusNode(String parameters) : IoTItem(parameters) + { + _addr = jsonReadInt(parameters, "addr"); // адреса slave прочитаем с веба + jsonRead(parameters, "reg", _regStr); // адреса регистров прочитаем с веба + jsonRead(parameters, "func", _funcStr); // Функция ModBUS + _countReg = jsonReadInt(parameters, "count"); + jsonRead(parameters, "float", _isFloat); + jsonRead(parameters, "debug", _debug); + _func = hexStringToUint8(_funcStr); + _reg = hexStringToUint16(_regStr); + } + + void doByInterval() + { + regEvent(readFunctionModBus(_func, _addr, _reg, _countReg, _isFloat), "register"); + } + + ~ModbusNode(){}; +}; + +class ModbusMaster : public IoTItem +{ +private: + int _rx = MODBUS_RX_PIN; // адреса прочитаем с веба + int _tx = MODBUS_TX_PIN; + int _baud = MODBUS_SERIAL_BAUD; + String _prot = "SERIAL_8N1"; + int protocol = SERIAL_8N1; + + int _addr = 0; // Адрес слейва от 1 до 247 ( вроде ) + String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????) + uint16_t _reg = 0; + bool _debug; // Дебаг + +public: + ModbusMaster(String parameters) : IoTItem(parameters) + { + _rx = jsonReadInt(parameters, "RX"); // прочитаем с веба + _tx = jsonReadInt(parameters, "TX"); + _DIR_PIN = jsonReadInt(parameters, "DIR_PIN"); + _baud = jsonReadInt(parameters, "baud"); + _prot = jsonReadStr(parameters, "protocol"); + jsonRead(parameters, "debug", _debug); + + if (_prot == "SERIAL_8N1") + { + protocol = SERIAL_8N1; + } + else if (_prot == "SERIAL_8N2") + { + protocol = SERIAL_8N2; + } + + pinMode(_DIR_PIN, OUTPUT); + digitalWrite(_DIR_PIN, LOW); + + // Serial2.begin(baud-rate, protocol, RX pin, TX pin); + + _modbusUART = new HardwareSerial(UART_LINE); + + if (_debug) + { + SerialPrint("I", "ModbusMaster", "baud: " + String(_baud) + ", protocol: " + String(protocol, HEX) + ", RX: " + String(_rx) + ", TX: " + String(_tx)); + } + ((HardwareSerial *)_modbusUART)->begin(_baud, protocol, _rx, _tx); // выбираем тип протокола, скорость и все пины с веба + ((HardwareSerial *)_modbusUART)->setTimeout(200); + + node.preTransmission(modbusPreTransmission); + node.postTransmission(modbusPostTransmission); + } + + // Комманды из сценария + IoTValue execute(String command, std::vector ¶m) + { + IoTValue val; + uint8_t result; + uint32_t reading; + + uint16_t _reg = 0; + uint8_t count = 1; + bool isFloat = 0; + if (command == "readInputRegisters") // vout = mb.readInputRegisters(1, "0х0000", 1, 0) - "Адрес","Регистр","Кличество регистров","1-float, 0-long" + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + count = (uint8_t)param[2].valD; + count = count > 2 ? 2 : count; + count = count < 1 ? 1 : count; + isFloat = (bool)param[3].valD; + val.valD = readFunctionModBus(0x04, _addr, _reg, count, isFloat); + } + return val; + } + else if (command == "readHoldingRegisters") // vout = mb.readHoldingRegisters(1, "0х0000", 2, 1) - "Адрес","Регистр","Кличество регистров","1-float, 0-long" + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + count = (uint8_t)param[2].valD; + count = count > 2 ? 2 : count; + count = count < 1 ? 1 : count; + isFloat = (bool)param[3].valD; + val.valD = readFunctionModBus(0x03, _addr, _reg, count, isFloat); + } + return val; + } + else if (command == "readCoils") // vout = mb.readCoils(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит" + { + if (param.size()) + { + count = (uint8_t)param[2].valD; + count = count > 16 ? 16 : count; + count = count < 1 ? 1 : count; + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + node.begin(_addr, (Stream &)*_modbusUART); + val.valD = readFunctionModBus(0x01, _addr, _reg, count); + } + return val; + } + else if (command == "readDiscreteInputs") // vout = mb.readDiscreteInputs(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит" + { + if (param.size()) + { + count = (uint8_t)param[2].valD; + count = count > 16 ? 16 : count; + count = count < 1 ? 1 : count; + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + node.begin(_addr, (Stream &)*_modbusUART); + val.valD = readFunctionModBus(0x02, _addr, _reg, count); + } + return val; + } + else if (command == "writeSingleRegister") // vout = mb.writeSingleRegister(1,"0x0003", 1) - addr, register, state + { + if (param.size()) + { + node.begin((uint8_t)param[0].valD, (Stream &)*_modbusUART); + + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + // bool state = param[2].valD; + uint16_t state = param[2].valD; + result = node.writeSingleRegister(_reg, state); + if (_debug) + { + SerialPrint("I", "ModbusMaster", "writeSingleRegister, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " = result: " + String(result, HEX)); + } + } + // Что можно вернуть в ответ на запись ??? + return {}; + } + else if (command == "writeSingleCoil") // vout = mb.writeSingleCoil(1,"0x0003", 1) - addr, register, state + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + node.begin(_addr, (Stream &)*_modbusUART); + + bool state = param[2].valD; + result = node.writeSingleCoil(_reg, state); + if (_debug) + { + SerialPrint("I", "ModbusMaster", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " = result: " + String(result, HEX)); + } + } + // Что можно вернуть в ответ на запись койлов??? + return {}; + } + else if (command == "writeMultipleCoils") // Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011 + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + count = (uint8_t)param[2].valD; + count = count > 16 ? 16 : count; + count = count < 1 ? 1 : count; + node.begin(_addr, (Stream &)*_modbusUART); + + uint16_t state = param[3].valD; + node.setTransmitBuffer(0, state); + result = node.writeMultipleRegisters(_reg, count); + node.clearTransmitBuffer(); + if (_debug) + { + SerialPrint("I", "ModbusMaster", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " = result: " + String(result, HEX)); + } + } + return {}; + } + // На данный момент записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола. + else if (command == "writeMultipleRegisters") // mb.writeMultipleRegisters(1, \"0х0000\", 1234.987) + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + node.begin(_addr, (Stream &)*_modbusUART); + + float state = param[2].valD; + + node.setTransmitBuffer(0, lowWord(state)); + node.setTransmitBuffer(1, highWord(state)); + result = node.writeMultipleRegisters(_reg, 2); + node.clearTransmitBuffer(); + if (_debug) + { + SerialPrint("I", "ModbusMaster", "writeMultipleRegisters, addr: " + String((uint8_t)_addr, HEX) + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " (" + String(state, HEX) + ")"); + } + } + return {}; + } + return val; + } + + ~ModbusMaster() + { + delete _modbusUART; + _modbusUART = nullptr; + }; +}; + +void *getAPI_ModbusRTU(String subtype, String param) +{ + + if (subtype == F("mbNode")) + { + return new ModbusNode(param); + } + else if (subtype == F("mbMaster")) + { + return new ModbusMaster(param); + } + { + return nullptr; + } +} diff --git a/src/modules/sensors/ModbusRTU/modinfo.json b/src/modules/sensors/ModbusRTU/modinfo.json new file mode 100644 index 00000000..8d7d7075 --- /dev/null +++ b/src/modules/sensors/ModbusRTU/modinfo.json @@ -0,0 +1,124 @@ +{ + "menuSection": "sensors", + "configItem": [ + { + "global": 0, + "name": "ModbusNode", + "type": "Reading", + "subtype": "mbNode", + "id": "mbNode", + "widget": "anydataTmp", + "page": "Modbus", + "descr": "Данные modbus", + "int": 5, + "func": "0x03", + "addr": 1, + "reg": "0x0000", + "count": 1, + "float": 1, + "round": 0, + "debug": 1 + }, + { + "global": 0, + "name": "ModbusMaster", + "type": "Reading", + "subtype": "mbMaster", + "id": "mb", + "widget": "anydataTmp", + "page": "Modbus", + "descr": "Настройки modbus", + "int": 5, + "RX": 18, + "TX": 19, + "DIR_PIN": 4, + "baud": 9600, + "protocol": "SERIAL_8N2", + "debug": 1 + } + ], + "about": { + "authorName": "Serghei Crasnicov", + "authorContact": "https://t.me/Serghei63", + "authorGit": "https://github.com/Serghei63", + "specialThanks": "Mit4bmw", + "moduleName": "ModbusRTU", + "moduleVersion": "2.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "subTypes": [ + "mbNode", + "mbMaster" + ], + "title": "ModbusMaster", + "moduleDesc": "Позволяет управлять оборудованием по протоколу modbus", + "propInfo": { + "func": "Функция modbus", + "addr": "Адрес slav", + "reg": "Адрес регистра", + "count": "Количество регистров. В ModbusNode 16-разядные регистры можно считать не более 2 в ноде. Для битовых данных не более 16 бит в ноде. Значение поместится в value Ноды", + "float": "Тип считываемых данных, если count=2, то данные могут быть long (указать 0) или float (указать 1). Числа Long не рекомендуются (на больших числах не будет точности), так как будет ограничено точность представление в IotManager идет во float", + "int": "Количество секунд между опросами датчика.", + "RX": "Пин RX", + "TX": "Пин TX", + "DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен", + "baud": "скорость Uart", + "protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2", + "debug": " 1 - включить вывод дебага , 0 - отключить дебаг" + }, + "funcInfo": [ + { + "name": "readCoils", + "descr": "Чтение койла (битового поля). Функция 0х01 протокола. Читает не более 16 бит за раз. Пример: mb.readCoils(1, \"0х0000\", 1)", + "params": ["Адрес","Регистр","Кличество бит"] + }, + { + "name": "readDiscreteInputs", + "descr": "Чтение дискретного выхода (битового поля). Функция 0х02 протокола. Читает не более 16 бит за раз. Пример: mb.readDiscreteInputs(1, \"0х0000\", 8)", + "params": ["Адрес","Регистр","Кличество бит"] + + }, + { + "name": "readHoldingRegisters", + "descr": "Запрос данных регистра. Функция 0х03 протокола. Читает не более двух регистров за раз. Пример: mb.readHoldingRegisters(1, \"0х0000\", 1, 0)", + "params": ["Адрес","Регистр","Кличество регистров","1-float, 0-long"] + }, + { + "name": "readInputRegisters", + "descr": "Запрос данных регистра. Функция 0х04 протокола. Читает не более двух регистров за раз. Пример: mb.readInputRegisters(1, \"0х0000\", 1, 0)", + "params": ["Адрес","Регистр","Кличество регистров","1-float, 0-long"] + }, + { + "name": "writeSingleCoils", + "descr": "Запись в койл (битовое поле) одного бита. Функция 0х05 протокола. Пример: mb.writeSingleCoils(1, \"0х0000\", 1)", + "params": ["Адрес","Регистр","Данные"] + }, + { + "name": "writeSingleRegister", + "descr": "Запись данных в один регистр. Функция 0х06 протокола. Пример: mb.writeSingleRegister(1, \"0х0000\", 128)", + "params": ["Адрес","Регистр","Данные"] + }, + { + "name": "writeMultipleCoils", + "descr": "Запись данных в несколько койлов до 16 (число от 0 до 65535). Функция 0x0F протокола. Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011", + "params": ["Адрес","Регистр","Кличество койлов (бит)","Данные"] + }, + { + "name": "writeMultipleRegisters", + "descr": "Запись данных в несколько регистров. На данный момент записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола. Пример: mb.writeMultipleRegisters(1, \"0х0000\", 1234.987)", + "params": ["Адрес","Регистр","Данные"] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32*": [ + "https://github.com/4-20ma/ModbusMaster" + ], + "esp82*": [ + "https://github.com/4-20ma/ModbusMaster" + ] + } +} \ No newline at end of file diff --git a/src/modules/sensors/ModbusRTUasync/ModbusRTU.cpp b/src/modules/sensors/ModbusRTUasync/ModbusRTU.cpp new file mode 100644 index 00000000..f6f346b5 --- /dev/null +++ b/src/modules/sensors/ModbusRTUasync/ModbusRTU.cpp @@ -0,0 +1,466 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include +#include + +#include "Logging.h" +#include "ModbusClientRTU.h" +#include "CoilData.h" + +// class ModbusUart; +Stream *_modbusUART = nullptr; + +// Данные Modbus по умолчанию +int8_t MODBUS_DIR_PIN = 0; +#define MODBUS_UART_LINE 2 +#define MODBUS_RX_PIN 18 // Rx pin +#define MODBUS_TX_PIN 19 // Tx pin +#define MODBUS_SERIAL_BAUD 9600 // Baud rate for esp32 and max485 communication + +// bool modBus_data_ready = false; +uint32_t modBus_Token_count = 0; // Счетчик токенов для Нод, 0 - всегду у главного класса, дальше по порядку +class ModbusNode; +std::map MBNoneMap; +ModbusClientRTU *MB = nullptr; + +ModbusClientRTU *instanceModBus(int8_t _DR) +{ + if (!MB) + { // Если библиотека ранее инициализировалась, т о просто вернем указатель + // Инициализируем библиотеку + if (_DR) + MB = new ModbusClientRTU(_DR); + else + MB = new ModbusClientRTU(); + } + return MB; +} +// ModbusClientRTU MB(_DIR_PIN); +// ModbusClientRTU MB(); + +class ModbusNode : public IoTItem +{ +private: + // Initialize the ModbusMaster object as node + // Инициализируем объект ModbusMaster как узел + + uint8_t _addr = 0; // Адрес слейва от 1 до 247 + String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????) + String _funcStr = ""; // Функция ModBUS + uint8_t _func; + uint16_t _reg = 0; + uint8_t _countReg = 1; + uint32_t _token = 0; + bool _isFloat = 0; + CoilData _respCoil; + +public: + ModbusNode(String parameters) : IoTItem(parameters) + { + _addr = jsonReadInt(parameters, "addr"); // адреса slave прочитаем с веба + jsonRead(parameters, "reg", _regStr); // адреса регистров прочитаем с веба + jsonRead(parameters, "func", _funcStr); // Функция ModBUS + jsonRead(parameters, "isFloat", _isFloat); + _countReg = jsonReadInt(parameters, "count"); + _func = hexStringToUint8(_funcStr); + _reg = hexStringToUint16(_regStr); + modBus_Token_count++; + _token = modBus_Token_count; + MBNoneMap[_token] = this; + Serial.printf("Добавлен нода/токен: %s - %d\n", getID(), _token); + } + + void doByInterval() + { + if (!MB) + { + Serial.printf("ModbusNode: ModbusClientAsync is NULL\n"); + return; + } + if (_func == 0x04) // vout = mb.readInputRegisters(1, "0х0000", 1, 0) - "Адрес","Регистр","Кличество регистров" + { + // val.valD = readFunctionModBus(0x04, _addr, _reg, count, isFloat); + Serial.printf("sending request with token %d\n", _token); + Error err; + err = MB->addRequest(_token, _addr, READ_INPUT_REGISTER, _reg, _countReg); + if (err != SUCCESS) + { + ModbusError e(err); + Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); + } + } + else if (_func == 0x03) // vout = mb.readHoldingRegisters(1, "0х0000", 2, 1) - "Адрес","Регистр","Кличество регистров" + { + Serial.printf("sending request with token %d\n", _token); + Error err; + err = MB->addRequest(_token, _addr, READ_HOLD_REGISTER, _reg, _countReg); + if (err != SUCCESS) + { + ModbusError e(err); + Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); + } + } + else if (_func == 0x01) // vout = mb.readCoils(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит" + { + Serial.printf("sending request with token %d\n", _token); + Error err; + err = MB->addRequest(_token, _addr, READ_COIL, _reg, _countReg); + if (err != SUCCESS) + { + ModbusError e(err); + Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); + } + } + else if (_func == 0x02) // vout = mb.readDiscreteInputs(1, \"0х0000\", 1) - "Адрес","Регистр","Кличество бит" + { + Serial.printf("sending request with token %d\n", _token); + Error err; + err = MB->addRequest(_token, _addr, READ_DISCR_INPUT, _reg, _countReg); + if (err != SUCCESS) + { + ModbusError e(err); + Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); + } + } + } + + void parseMB(ModbusMessage response) + { + if (MB) + { + if (_func == 0x02 || _func == 0x01) // coil + { + uint16_t val; + // response.get(3, val); + // regEvent((float)val, "ModbusNode"); + CoilData cd(_countReg); + cd.set(0, _countReg, (uint8_t *)response.data() + 3); + _respCoil = cd; + cd.print("Received : ", Serial); + val = cd[0]; + regEvent(val, "ModbusNode"); + } + else + { + if (_countReg == 2 && _isFloat) + { + float val; + response.get(3, val); + regEvent(val, "ModbusNode"); + } + else + { + if (_countReg == 2) + { + uint16_t val1, val2; + response.get(3, val1); + response.get(5, val2); + Serial.printf("COUNT 2: %02X - %02X\n", (int)val1, (int)val2); + long val = val1 | val2 << 16; + regEvent((float)val, "ModbusNode"); + } + else + { + uint16_t val; + response.get(3, val); + regEvent((float)val, "ModbusNode"); + } + } + } + } + } + + // Комманды из сценария + IoTValue execute(String command, std::vector ¶m) + { + IoTValue val; + // uint8_t result; + // uint32_t reading; + + uint16_t _index = 0; + + if (command == "getBits") + { + if (param.size()) + { + if (_respCoil.size() > _index) + { + _index = param[0].valD; + val.valD = _respCoil[_index]; + return val; + } + } + } + return {}; + } + + ~ModbusNode() + { + //MBNoneMap.erase(_token); + }; +}; + +// Define an onData handler function to receive the regular responses +// Arguments are received response message and the request's token +void handleModBusData(ModbusMessage response, uint32_t token) +{ + printf("Response --- Token:%d FC:%02X Server:%d Length:%d\n", + token, + response.getFunctionCode(), + response.getServerID(), + response.size()); + HEXDUMP_N("Data dump", response.data(), response.size()); + + // // First value is on pos 3, after server ID, function code and length byte + // uint16_t offs = 3; + // // The device has values all as IEEE754 float32 in two consecutive registers + // offs = response.get(offs, values[i]); + // uint16_t val; + // response.get(3, val); + if (MBNoneMap[token]) + { + MBNoneMap[token]->parseMB(response); + } + else + { + Serial.printf("Токен/Нода не найден: %d\n", token); + } + // modBus_data_ready = true; +} + +// Define an onError handler function to receive error responses +// Arguments are the error code returned and a user-supplied token to identify the causing request +void handleModBusError(Error error, uint32_t token) +{ + // ModbusError wraps the error code and provides a readable error message for it + ModbusError me(error); + // LOG_E("Error response: %02X - %s\n", (int)me, (const char *)me); + Serial.printf("Error response: %02X - %s\n", (int)me, (const char *)me); +} + +class ModbusClientAsync : public IoTItem +{ +private: + int8_t _rx = MODBUS_RX_PIN; // адреса прочитаем с веба + int8_t _tx = MODBUS_TX_PIN; + int _baud = MODBUS_SERIAL_BAUD; + String _prot = "SERIAL_8N1"; + int protocol = SERIAL_8N1; + + int _addr = 0; // Адрес слейва от 1 до 247 ( вроде ) + String _regStr = ""; // Адрес регистра который будем дергать ( по коду от 0х0000 до 0х????) + uint16_t _reg = 0; + bool _debug; // Дебаг + uint32_t _token = 0; // Токен у главного класса весгда 0 + +public: + ModbusClientAsync(String parameters) : IoTItem(parameters) + { + _rx = (int8_t)jsonReadInt(parameters, "RX"); // прочитаем с веба + _tx = (int8_t)jsonReadInt(parameters, "TX"); + MODBUS_DIR_PIN = (int8_t)jsonReadInt(parameters, "DIR_PIN"); + _baud = jsonReadInt(parameters, "baud"); + _prot = jsonReadStr(parameters, "protocol"); + jsonRead(parameters, "debug", _debug); + + if (_prot == "SERIAL_8N1") + { + protocol = SERIAL_8N1; + } + else if (_prot == "SERIAL_8N2") + { + protocol = SERIAL_8N2; + } + + pinMode(MODBUS_DIR_PIN, OUTPUT); + digitalWrite(MODBUS_DIR_PIN, LOW); + + // Serial2.begin(baud-rate, protocol, RX pin, TX pin); + instanceModBus(MODBUS_DIR_PIN); + _modbusUART = new HardwareSerial(MODBUS_UART_LINE); + + if (_debug) + { + SerialPrint("I", "ModbusClientAsync", "baud: " + String(_baud) + ", protocol: " + String(protocol, HEX) + ", RX: " + String(_rx) + ", TX: " + String(_tx)); + } + RTUutils::prepareHardwareSerial((HardwareSerial &)*_modbusUART); + // Serial2.begin(BAUDRATE, SERIAL_8N1, RXPIN, TXPIN); + ((HardwareSerial *)_modbusUART)->begin(_baud, protocol, _rx, _tx); // выбираем тип протокола, скорость и все пины с веба + ((HardwareSerial *)_modbusUART)->setTimeout(200); + + // Set up ModbusRTU client. + // - provide onData handler function + MB->onDataHandler(&handleModBusData); + // - provide onError handler function + MB->onErrorHandler(&handleModBusError); + // Set message timeout to 2000ms + MB->setTimeout(2000); + // Start ModbusRTU background task + MB->begin((HardwareSerial &)*_modbusUART); + // MBNoneMap[_token] = this; + } + + // Комманды из сценария + IoTValue execute(String command, std::vector ¶m) + { + IoTValue val; + // uint8_t result; + // uint32_t reading; + + uint16_t _reg = 0; + uint8_t count = 1; + if (command == "writeSingleRegister") // vout = mb.writeSingleRegister(1,"0x0003", 1) - addr, register, state + { + if (param.size()) + { + // node.begin((uint8_t)param[0].valD, (Stream &)*_modbusUART); + + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + // bool state = param[2].valD; + uint16_t state = param[2].valD; + // result = node.writeSingleRegister(_reg, state); + if (_debug) + { + SerialPrint("I", "ModbusClientAsync", "writeSingleRegister, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state)); + } + + // We will first set the register to a known state, read the register, + // then write to it and finally read it again to verify the change + + // Set defined conditions first - write 0x1234 to the register + // The Token value is used in handleData to avoid the output for this first preparation request! + // uint32_t Token = 1111; + Serial.printf("sending request with token %d\n", _token); + Error err; + err = MB->addRequest(_token, _addr, WRITE_HOLD_REGISTER, _reg, state); + if (err != SUCCESS) + { + ModbusError e(err); + Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); + } + } + // Что можно вернуть в ответ на запись ??? + return {}; + } + else if (command == "writeSingleCoil") // vout = mb.writeSingleCoil(1,"0x0003", 1) - addr, register, state + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + // node.begin(_addr, (Stream &)*_modbusUART); + + bool state = param[2].valD; + // result = node.writeSingleCoil(_reg, state); + if (_debug) + { + SerialPrint("I", "ModbusClientAsync", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state)); + } + + // next set a single coil at 8 + Serial.printf("sending request with token %d\n", _token); + Error err; + ModbusMessage msg; + if (state) + { + msg.setMessage(_addr, WRITE_COIL, _reg, 0xFF00); + err = MB->addRequest(msg, _token); + } + else + { + // msg.setMessage(_addr, WRITE_COIL, _reg, 0x0000); + err = MB->addRequest(_token, _addr, WRITE_COIL, _reg, 0); + } + if (err != SUCCESS) + { + ModbusError e(err); + Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); + } + } + // Что можно вернуть в ответ на запись койлов??? + return {}; + } + else if (command == "writeMultipleCoils") // Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011 + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + count = (uint8_t)param[2].valD; + count = count > 16 ? 16 : count; + count = count < 1 ? 1 : count; + // node.begin(_addr, (Stream &)*_modbusUART); + + uint16_t state = param[3].valD; + // node.setTransmitBuffer(0, state); + // result = node.writeMultipleRegisters(_reg, count); + // node.clearTransmitBuffer(); + Serial.printf("NOT SUPPORTED!\n"); + if (_debug) + { + SerialPrint("I", "ModbusClientAsync", "writeSingleCoil, addr: " + String((uint8_t)_addr, HEX) + ", regStr: " + _regStr + ", reg: " + String(_reg, HEX) + ", state: " + String(state)); + } + + CoilData cd(12); + // Finally set a a bunch of coils starting at 20 + cd = "011010010110"; + Serial.printf("sending request with token %d\n", _token); + Error err; + err = MB->addRequest(_token, _addr, WRITE_MULT_COILS, _reg, cd.coils(), cd.size(), cd.data()); + if (err != SUCCESS) + { + ModbusError e(err); + Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); + } + } + return {}; + } + // На данный момент записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола. + else if (command == "writeMultipleRegisters") // mb.writeMultipleRegisters(1, \"0х0000\", 1234.987) + { + if (param.size()) + { + _addr = param[0].valD; + _reg = hexStringToUint16(param[1].valS); + // node.begin(_addr, (Stream &)*_modbusUART); + + float state = param[2].valD; + + // node.setTransmitBuffer(0, lowWord(state)); + // node.setTransmitBuffer(1, highWord(state)); + // result = node.writeMultipleRegisters(_reg, 2); + // node.clearTransmitBuffer(); + Serial.printf("NOT SUPPORTED!\n"); + if (_debug) + { + SerialPrint("I", "ModbusClientAsync", "writeMultipleRegisters, addr: " + String((uint8_t)_addr, HEX) + ", reg: " + String(_reg, HEX) + ", state: " + String(state) + " (" + String(state, HEX) + ")"); + } + } + return {}; + } + return val; + } + + ~ModbusClientAsync() + { + delete _modbusUART; + _modbusUART = nullptr; + MBNoneMap.clear(); + }; +}; + +void *getAPI_ModbusRTUasync(String subtype, String param) +{ + if (subtype == F("mbNode")) + { + return new ModbusNode(param); + } + else if (subtype == F("mbClient")) + { + return new ModbusClientAsync(param); + } + { + return nullptr; + } +} diff --git a/src/modules/sensors/ModbusRTUasync/modinfo.json b/src/modules/sensors/ModbusRTUasync/modinfo.json new file mode 100644 index 00000000..429f6059 --- /dev/null +++ b/src/modules/sensors/ModbusRTUasync/modinfo.json @@ -0,0 +1,103 @@ +{ + "menuSection": "sensors", + "configItem": [ + { + "global": 0, + "name": "ModbusNode", + "type": "Reading", + "subtype": "mbNode", + "id": "mbNode", + "widget": "anydataTmp", + "page": "Modbus", + "descr": "Чтение данных modbus", + "int": 5, + "func": "0x03", + "addr": 1, + "reg": "0x0000", + "count": 1, + "isFloat": 0, + "round": 0 + }, + { + "global": 0, + "name": "ModbusAsunc", + "type": "Reading", + "subtype": "mbClient", + "id": "mb", + "widget": "anydataTmp", + "page": "Modbus", + "descr": "Настройки и запись modbus", + "int": 5, + "RX": 18, + "TX": 19, + "DIR_PIN": 4, + "baud": 9600, + "protocol": "SERIAL_8N2", + "debug": 1 + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "Serghei Crasnicov", + "moduleName": "ModbusRTUasync", + "moduleVersion": "1.1", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "subTypes": [ + "mbClient" + ], + "title": "ModbusAsync", + "moduleDesc": "Позволяет управлять оборудованием по протоколу modbus. Модуль ModbusAsync обязательный для настройки. Запись через функции сценария в ModbusAsync. Для чтение регистров добавлять модули ModbusNode.", + "propInfo": { + "int": "Количество секунд между опросами датчика.", + "RX": "Пин RX", + "TX": "Пин TX", + "DIR_PIN": "connect DR, RE pin of MAX485 to gpio, указать 0 если не нужен", + "baud": "скорость Uart", + "protocol": "Протокол Uart: SERIAL_8N1 или SERIAL_8N2", + "debug": " 1 - включить вывод дебага , 0 - отключить дебаг", + "func": "Функция чтения modbus (0x01, 0x02, 0x03, 0x04)", + "addr": "Адрес slave", + "reg": "Адрес регистра", + "count": "Количество регистров. В ModbusNode 16-разядные регистры можно считать не более 2 в ноде. Для битовых данных не более 16 бит в ноде. Значение поместится в value Ноды", + "isFloat": "Тип считываемых данных, если count=2, то данные могут быть long (указать 0) или float (указать 1). Числа Long не рекомендуются (на больших числах не будет точности), так как будет ограничено точность представление в IotManager идет во float" + }, + "funcInfo": [ + { + "name": "writeSingleCoils", + "descr": "Запись в койл (битовое поле) одного бита, вызывать из ModbusAsunc. Функция 0х05 протокола. Пример: mb.writeSingleCoils(1, \"0х0000\", 1)", + "params": ["Адрес","Регистр","Данные"] + }, + { + "name": "writeSingleRegister", + "descr": "Запись данных в один регистр, вызывать из ModbusAsunc. Функция 0х06 протокола. Пример: mb.writeSingleRegister(1, \"0х0000\", 128)", + "params": ["Адрес","Регистр","Данные"] + }, + { + "name": "writeMultipleCoils", + "descr": "В разработке! Запись данных в несколько койлов до 16 (число от 0 до 65535), вызывать из ModbusAsunc. Функция 0x0F протокола. Пример: mb.writeMultipleCoils(1, \"0х0000\", 4, 3) - будут записаны в четыре бита 0011", + "params": ["Адрес","Регистр","Кличество койлов (бит)","Данные"] + }, + { + "name": "writeMultipleRegisters", + "descr": "В разработке! Запись данных в несколько регистров, вызывать из ModbusAsunc. Записывает 2(два) регистра!!!!! Подходит для записи float?? Функция 0х10 протокола. Пример: mb.writeMultipleRegisters(1, \"0х0000\", 1234.987)", + "params": ["Адрес","Регистр","Данные"] + }, + { + "name": "getBits", + "descr": "Получить из ModbusNode бит по его номеру, если ранее считали из слейва F0x01 или F0x02 более одного бита. Вернёт -1 если данных нет. Пример: mbNode.getCoil(0) получит первый из считанных бит", + "params": ["индекс бита"] + } + ] + }, + "defActive": false, + "usedLibs": { + "esp32*": [ + "https://github.com/eModBus/eModBus" + ] + } +} \ No newline at end of file diff --git a/src/modules/sensors/Ntc/modinfo.json b/src/modules/sensors/Ntc/modinfo.json index 6e0dc849..f7876979 100644 --- a/src/modules/sensors/Ntc/modinfo.json +++ b/src/modules/sensors/Ntc/modinfo.json @@ -48,7 +48,7 @@ "Beta": "Beta термистора" } }, - "defActive": false, + "defActive": true, "usedLibs": { "esp32*": [], "esp82*": [] diff --git a/src/modules/exec/Pcf8591/Pcf8591.cpp b/src/modules/sensors/Pcf8591/Pcf8591.cpp similarity index 82% rename from src/modules/exec/Pcf8591/Pcf8591.cpp rename to src/modules/sensors/Pcf8591/Pcf8591.cpp index ff7d45bc..e3d2511d 100644 --- a/src/modules/exec/Pcf8591/Pcf8591.cpp +++ b/src/modules/sensors/Pcf8591/Pcf8591.cpp @@ -5,10 +5,13 @@ #include // Make sure that this is set to the value in volts of VCC -#define ADC_REFERENCE_VOLTAGE 3.3 +//#define ADC_REFERENCE_VOLTAGE 3.3 +// Make sure that this is set to the value in volts of VCC +//#define ADC_REFERENCE_VOLTAGE 5.0 class Pcf8591 : public IoTItem { int _pin; + float _adc_ref; bool _isRaw; bool _isInited = false; Adafruit_PCF8591 pcf = Adafruit_PCF8591(); @@ -19,6 +22,9 @@ class Pcf8591 : public IoTItem { jsonRead(parameters, "pin", tmp); _pin = tmp.toInt(); + jsonRead(parameters, "adc_ref", tmp); + _adc_ref = tmp.toFloat(); + jsonRead(parameters, "mode", tmp); _isRaw = tmp == "raw"; @@ -48,7 +54,8 @@ class Pcf8591 : public IoTItem { if (_isRaw) value.valD = pcf.analogRead(_pin); // Чтение АЦП нулевого канала (Вольты) else - value.valD = (int_to_volts(pcf.analogRead(_pin), 8, ADC_REFERENCE_VOLTAGE)); + // value.valD = (int_to_volts(pcf.analogRead(_pin), 8, ADC_REFERENCE_VOLTAGE)); + value.valD = (int_to_volts(pcf.analogRead(_pin), 8, _adc_ref)); regEvent(value.valD, "PCF8591"); } diff --git a/src/modules/exec/Pcf8591/modinfo.json b/src/modules/sensors/Pcf8591/modinfo.json similarity index 98% rename from src/modules/exec/Pcf8591/modinfo.json rename to src/modules/sensors/Pcf8591/modinfo.json index 183b354b..99fdc606 100644 --- a/src/modules/exec/Pcf8591/modinfo.json +++ b/src/modules/sensors/Pcf8591/modinfo.json @@ -12,6 +12,7 @@ "descr": "PCF_0", "pin": "0", "mode": "volt", + "adc_ref": 5.0, "map": "1,255,1,100", "plus": 0, "multiply": 1, diff --git a/src/modules/sensors/Pzem004t_v2/modinfo.json b/src/modules/sensors/Pzem004t_v2/modinfo.json index 57008f54..6b543024 100644 --- a/src/modules/sensors/Pzem004t_v2/modinfo.json +++ b/src/modules/sensors/Pzem004t_v2/modinfo.json @@ -142,18 +142,9 @@ "btn-reset": "pzem будет сброшен к нулю. Смотрите в логе результат: [i] Pzem reset done" } }, - "defActive": true, + "defActive": false, "usedLibs": { - "esp32_4mb": [], - "esp32_4mb3f": [], - "esp32cam_4mb": [], - "esp32c3m_4mb": [], - "esp8266_4mb": [], - "esp8266_1mb": [], - "esp8266_1mb_ota": [], - "esp8285_1mb": [], - "esp8285_1mb_ota": [], - "esp8266_2mb": [], - "esp8266_2mb_ota": [] + "esp32*": [], + "esp82*": [] } } \ No newline at end of file diff --git a/src/modules/sensors/RTC/modinfo.json b/src/modules/sensors/RTC/modinfo.json index 369d70c3..2eff1496 100644 --- a/src/modules/sensors/RTC/modinfo.json +++ b/src/modules/sensors/RTC/modinfo.json @@ -28,6 +28,7 @@ "exampleURL": "https://iotmanager.org/wiki", "specialThanks": "", "moduleName": "RTC", + "moduleDefines": ["mod_RtcDriver", "hardRTC=1"], "moduleVersion": "1.0", "usedRam": { "esp32_4mb": 15, diff --git a/src/modules/sensors/S8/modinfo.json b/src/modules/sensors/S8/modinfo.json index 5aeadc3f..eab320b8 100644 --- a/src/modules/sensors/S8/modinfo.json +++ b/src/modules/sensors/S8/modinfo.json @@ -34,7 +34,7 @@ "int": "Количество секунд между опросами датчика." } }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [], "esp82*": [] diff --git a/src/modules/sensors/Sht20/modinfo.json b/src/modules/sensors/Sht20/modinfo.json index fdded673..b2feed28 100644 --- a/src/modules/sensors/Sht20/modinfo.json +++ b/src/modules/sensors/Sht20/modinfo.json @@ -47,7 +47,7 @@ "int": "Количество секунд между опросами датчика." } }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "robtillaart/SHT2x@^0.1.1" diff --git a/src/modules/sensors/Sht30/Sht30.cpp b/src/modules/sensors/Sht30/Sht30.cpp index d78947df..fb4a5828 100644 --- a/src/modules/sensors/Sht30/Sht30.cpp +++ b/src/modules/sensors/Sht30/Sht30.cpp @@ -13,19 +13,33 @@ #include "Wire.h" #include -SHT3X sht30(0x45); +SHT3X sht30(0x44); class Sht30t : public IoTItem { + + private: + uint8_t _addr = 0; + public: - Sht30t(String parameters): IoTItem(parameters) { } + Sht30t(String parameters): IoTItem(parameters) { + { + String sAddr; + jsonRead(parameters, "addr", sAddr); + if (sAddr == "") + scanI2C(); + else + _addr = hexStringToUint8(sAddr); + } + + } void doByInterval() { if(sht30.get()==0){ value.valD = sht30.cTemp; - SerialPrint("E", "Sensor Sht30t", "OK"); + SerialPrint("i", "Sensor Sht30t", "OK"); - if (value.valD < -46.85F) regEvent(value.valD, "Sht30t"); // TODO: найти способ понимания ошибки получения данных + if (value.valD > -46.85F) regEvent(value.valD, "Sht30t"); // TODO: найти способ понимания ошибки получения данных else SerialPrint("E", "Sensor Sht30t", "Error", _id); } } @@ -33,14 +47,27 @@ class Sht30t : public IoTItem { }; class Sht30h : public IoTItem { + + private: + uint8_t _addr = 0; + public: - Sht30h(String parameters): IoTItem(parameters) { } + Sht30h(String parameters): IoTItem(parameters) { + { + String sAddr; + jsonRead(parameters, "addr", sAddr); + if (sAddr == "") + scanI2C(); + else + _addr = hexStringToUint8(sAddr); + } + } void doByInterval() { if(sht30.get()==0){ value.valD = sht30.humidity; - SerialPrint("E", "Sensor Sht30h", "OK"); + SerialPrint("i", "Sensor Sht30h", "OK"); if (value.valD != -6) regEvent(value.valD, "Sht30h"); // TODO: найти способ понимания ошибки получения данных else SerialPrint("E", "Sensor Sht30h", "Error", _id); } diff --git a/src/modules/sensors/Sht30/modinfo.json b/src/modules/sensors/Sht30/modinfo.json index 6a22d155..a9196559 100644 --- a/src/modules/sensors/Sht30/modinfo.json +++ b/src/modules/sensors/Sht30/modinfo.json @@ -10,6 +10,7 @@ "widget": "anydataTmp", "page": "Сенсоры", "descr": "SHT30 Температура", + "addr": "", "int": 15, "round": 1 }, @@ -22,6 +23,7 @@ "widget": "anydataHum", "page": "Сенсоры", "descr": "SHT30 Влажность", + "addr": "", "int": 15, "round": 1 } @@ -47,7 +49,7 @@ "int": "Количество секунд между опросами датчика." } }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [ "WEMOS SHT3x@1.0.0" diff --git a/src/modules/sensors/Sonar/modinfo.json b/src/modules/sensors/Sonar/modinfo.json index 737ecd3f..de6fcd56 100644 --- a/src/modules/sensors/Sonar/modinfo.json +++ b/src/modules/sensors/Sonar/modinfo.json @@ -35,7 +35,7 @@ "int": "Количество секунд между опросами датчика." } }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32*": [], "esp82*": [] diff --git a/src/modules/virtual/Cron/modinfo.json b/src/modules/virtual/Cron/modinfo.json index 26f9fbab..b061e458 100644 --- a/src/modules/virtual/Cron/modinfo.json +++ b/src/modules/virtual/Cron/modinfo.json @@ -50,6 +50,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp b/src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp new file mode 100644 index 00000000..3c659755 --- /dev/null +++ b/src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp @@ -0,0 +1,259 @@ +#include "Global.h" +#include "classes/IoTDiscovery.h" + +class DiscoveryHA : public IoTDiscovery +{ +private: + String _topic = ""; + bool sendOk = false; + // bool topicOk = false; + bool HA = false; +public: + DiscoveryHA(String parameters) : IoTDiscovery(parameters) + { + _topic = jsonReadStr(parameters, "topic"); + if (_topic && _topic != "" && _topic != "null") + { + HA = true; + HATopic = _topic; + } + if (mqttIsConnect() && HA) + { +#if defined ESP32 +// пре реконнекте вызывается отправка всех виджетов из файла layout.json в HA +// на ESP8266 мало оперативки и это можно делать только до момента конфигурации +// mqttReconnect(); +#endif + // sendOk = true; + // mqttSubscribeExternal(_topic); + } + } + /* + void onMqttRecive(String &topic, String &msg) + { + if (!HA) + return; + + if (msg.indexOf("HELLO") == -1) + { + String dev = selectToMarkerLast(topic, "/"); + dev.toUpperCase(); + dev.replace(":", ""); + if (_topic != topic) + { + // SerialPrint("i", "ExternalMQTT", _id + " not equal: " + topic + " msg: " + msg); + return; + } + // обработка топика, на который подписались + } + } */ + + void doByInterval() + { + /* // периодически проверяем связь с MQTT брокером и если она появилась, то подписываемся на нужный топик + if (mqttIsConnect() && !sendOk && &&topicOk) + { + sendOk = true; + getlayoutHA(); + publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}"); + //mqttSubscribeExternal(_topic); + } + + // если нет коннектас брокером, то сбрасываем флаг подписки, что бы при реконекте заново подписаться + if (!mqttIsConnect()) + sendOk = false; */ + } + /* String getMqttExterSub() + { + return _topic; + } */ + + void mqttSubscribeDiscovery() + { + if (HA) + { + getlayoutHA(); + publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}"); + } + } + + void getlayoutHA() + { + if (HA) + { + auto file = seekFile("layout.json"); + if (!file) + { + SerialPrint("E", F("MQTT"), F("no file layout.json")); + return; + } + size_t size = file.size(); + DynamicJsonDocument doc(size * 2); + DeserializationError error = deserializeJson(doc, file); + if (error) + { + SerialPrint("E", F("MQTT"), error.f_str()); + jsonWriteInt(errorsHeapJson, F("jse3"), 1); // Ошибка чтения json файла с виджетами при отправки в mqtt + } + int i = 0; + // String HATopic = jsonReadStr(settingsFlashJson, F("HomeAssistant")); + JsonArray arr = doc.as(); + for (JsonVariant value : arr) + { + String dev = selectToMarkerLast(value["topic"].as(), "/"); + dev.replace(":", ""); + String HAjson = ""; + HAjson = "{\"availability\":[{\"topic\": \"" + mqttRootDevice + "/state\",\"value_template\": \"{{ value_json.status }}\"}],\"availability_mode\": \"any\","; + HAjson = HAjson + " \"device\": {\"identifiers\": [\"" + mqttRootDevice + value["page"].as() + "\"],"; + HAjson = HAjson + " \"name\": \" " + value["page"].as() + "\"},"; + HAjson = HAjson + " \"name\": \"" + value["descr"].as() + "\","; + HAjson = HAjson + " \"state_topic\": \"" + value["topic"].as() + "/status\","; + HAjson = HAjson + " \"icon\": \"hass:none\","; + + // сенсоры + if (value["name"].as() == "anydataTmp") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"unit_of_measurement\": \"°C\""; + } + else if (value["name"].as() == "anydataHum") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"unit_of_measurement\": \"%\""; + } + else if (value["name"].as() == "anydataMm") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"unit_of_measurement\": \"mm\""; + } + else if (value["name"].as() == "anydataBar") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"unit_of_measurement\": \"Bar\""; + } + else if (value["name"].as() == "anydataPpm") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"unit_of_measurement\": \"ppm\""; + } + + // ввод числаФВ + else if (value["name"].as() == "inputDgt") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as() + "/control\","; + HAjson = HAjson + " \"mode\": \"box\","; + HAjson = HAjson + " \"min\": " + -1000000 + ","; + HAjson = HAjson + " \"max\": " + 1000000 + ""; + } + // ввод текста inputTxt + else if (value["name"].as() == "inputTxt") + { + HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as() + "/control\""; + } + // переключатель + else if (value["name"].as() == "toggle") + { + HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\","; + HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as() + "/control\","; + HAjson = HAjson + " \"device_class\": \"switch\","; + HAjson = HAjson + " \"payload_off\": " + 0 + ","; + HAjson = HAjson + " \"payload_on\": " + 1 + ","; + HAjson = HAjson + " \"state_off\": " + 0 + ","; + HAjson = HAjson + " \"state_on\": " + 1 + ""; + } + else + { + HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + mqttRootDevice + dev + "\""; + } + + HAjson = HAjson + " }"; + // "has_entity_name" : false, + + // SerialPrint("E", F("MQTT"), HAjson); + // текст + if (value["widget"].as() == "anydata") + { + + if (!publishRetain(HATopic + "/sensor/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + + // ввод числа + if (value["name"].as() == "inputDgt") + { + + if (!publishRetain(HATopic + "/number/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + // ввод текста inputTxt + if (value["name"].as() == "inputTxt") + { + + if (!publishRetain(HATopic + "/text/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + // переключатель + if (value["name"].as() == "toggle") + { + + if (!publishRetain(HATopic + "/switch/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + + i++; + } + file.close(); + + publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}"); + + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->iAmLocal) + { + publishStatusMqtt((*it)->getID(), (*it)->getValue()); + (*it)->onMqttWsAppConnectEvent(); + } + } + } + } + + IoTDiscovery *getHADiscovery() + { + if (HA) + return this; + else + return nullptr; + } + ~DiscoveryHA(){}; +}; + +void *getAPI_DiscoveryHA(String subtype, String param) +{ + if (subtype == F("DiscoveryHA")) + { + return new DiscoveryHA(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/virtual/DiscoveryHA/modinfo.json b/src/modules/virtual/DiscoveryHA/modinfo.json new file mode 100644 index 00000000..300a8f90 --- /dev/null +++ b/src/modules/virtual/DiscoveryHA/modinfo.json @@ -0,0 +1,40 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "Discovery of HA", + "type": "Reading", + "subtype": "DiscoveryHA", + "id": "ha", + "widget": "", + "page": "", + "descr": "", + "topic": "homeassistant" + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "@AVAKS", + "moduleName": "DiscoveryHA", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "DiscoveryHA", + "moduleDesc": "Модуль проброса данных в MQTT для HomeAssistant", + "propInfo": { + "topic":"Топик HomeAssistant" + } + + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [], + "bk72*": [] + } +} \ No newline at end of file diff --git a/src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp b/src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp new file mode 100644 index 00000000..8872651e --- /dev/null +++ b/src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp @@ -0,0 +1,295 @@ +#include "Global.h" +#include "classes/IoTDiscovery.h" +// #include "MqttDiscovery.h" +class DiscoveryHomeD : public IoTDiscovery +{ +private: + String _topic = ""; + bool sendOk = false; + // bool topicOk = false; + bool HOMEd = false; + int _names = 0; + String esp_id = chipId; + +public: + DiscoveryHomeD(String parameters) : IoTDiscovery(parameters) + { + _topic = jsonReadStr(parameters, "topic"); + _names = jsonReadInt(parameters, "names"); + if (_topic && _topic != "" && _topic != "null") + { + HOMEd = true; + HOMEdTopic = _topic; + } + if (_names) + { + esp_id = jsonReadStr(settingsFlashJson, F("name")); + jsonWriteInt(settingsFlashJson, F("HOMEd_names"), 1); + } + else + { + jsonWriteInt(settingsFlashJson, F("HOMEd_names"), 0); + } + + if (mqttIsConnect() && HOMEd) + { + mqttReconnect(); + // sendOk = true; + // mqttSubscribeExternal(_topic); + } + } + + void onMqttRecive(String &topic, String &payloadStr) + { + if (!HOMEd) + return; + + if (payloadStr.indexOf("HELLO") == -1) + { + /* String dev = selectToMarkerLast(topic, "/"); + dev.toUpperCase(); + dev.replace(":", ""); + if (_topic != topic) + { + // SerialPrint("i", "ExternalMQTT", _id + " not equal: " + topic + " msg: " + msg); + return; + } */ + // обработка топика, на который подписались + if (topic.indexOf(F("/td/custom")) != -1) + { + + // обрабатываем команды из HOMEd + StaticJsonDocument<200> doc; + deserializeJson(doc, payloadStr); + for (JsonPair kvp : doc.as()) + { + + String key = kvp.key().c_str(); + String value = kvp.value().as(); + if (key.indexOf(F("status_")) != -1) + { + key.replace("status_", ""); + if (value == "on") + { + generateOrder(key, "1"); + } + else if (value == "off") + { + generateOrder(key, "0"); + } + else if (value == "toggle") + { + String val = (String)(1 - getItemValue(key).toInt()); + generateOrder(key, val); + } + } + else + { + if (!value) + { + float val = kvp.value(); + generateOrder(key, (String)(val)); + } + else + { + generateOrder(key, value); + } + } + } + + SerialPrint("i", F("=>MQTT"), "Msg from HOMEd: " + payloadStr); + } + } + } + + void doByInterval() + { + /* // периодически проверяем связь с MQTT брокером и если она появилась, то подписываемся на нужный топик + if (mqttIsConnect() && !sendOk && topicOk) + { + sendOk = true; + publishRetain(_topic + "/device/custom/" + esp_id, "{\"status\":\"online\"}"); + String HOMEdsubscribeTopic = _topic + "/td/custom/" + esp_id; + // mqtt.subscribe(HOMEdsubscribeTopic.c_str()); + mqttSubscribeExternal(HOMEdsubscribeTopic); + } + + // если нет коннектас брокером, то сбрасываем флаг подписки, что бы при реконекте заново подписаться + if (!mqttIsConnect()) + sendOk = false; */ + } + + void publishStatusHOMEd(const String &topic, const String &data) + { + String path_h = HOMEdTopic + "/fd/custom/" + esp_id; + String json_h = "{}"; + if (topic != "onStart") + { + if (data.toInt() == 1) + { + jsonWriteStr(json_h, "status_" + topic, "on"); + } + else if (data.toInt() == 0) + { + jsonWriteStr(json_h, "status_" + topic, "off"); + } + if (data.toFloat()) + { + jsonWriteFloat(json_h, topic, data.toFloat()); + } + else + { + jsonWriteStr(json_h, topic, data); + } + mqtt.publish(path_h.c_str(), json_h.c_str(), false); + } + } + + void mqttSubscribeDiscovery() + { + if (HOMEd) + { + deleteFromHOMEd(); + getlayoutHOMEd(); + publishRetain(HOMEdTopic + "/device/custom/" + esp_id, "{\"status\":\"online\"}"); + String HOMEdsubscribeTopic = HOMEdTopic + "/td/custom/" + esp_id; + mqtt.subscribe(HOMEdsubscribeTopic.c_str()); + } + } + + void getlayoutHOMEd() + { + if (HOMEd) + { + String devName = jsonReadStr(settingsFlashJson, F("name")); + + auto file = seekFile("layout.json"); + if (!file) + { + SerialPrint("E", F("MQTT"), F("no file layout.json")); + return; + } + size_t size = file.size(); + DynamicJsonDocument doc(size * 2); + DeserializationError error = deserializeJson(doc, file); + if (error) + { + SerialPrint("E", F("MQTT"), error.f_str()); + jsonWriteInt(errorsHeapJson, F("jse3"), 1); // Ошибка чтения json файла с виджетами при отправки в mqtt + } + int i = 0; + // String path = jsonReadStr(settingsFlashJson, F("HOMEd")); + JsonArray arr = doc.as(); + String HOMEdJSON = ""; + HOMEdJSON = "{\"action\":\"updateDevice\","; + HOMEdJSON = HOMEdJSON + "\"device\":\"" + chipId + "\","; + HOMEdJSON = HOMEdJSON + "\"data\":{"; + HOMEdJSON = HOMEdJSON + "\"active\": true,"; + HOMEdJSON = HOMEdJSON + "\"cloud\": false,"; + HOMEdJSON = HOMEdJSON + "\"discovery\": false,"; + HOMEdJSON = HOMEdJSON + "\"id\":\"" + chipId + "\","; + HOMEdJSON = HOMEdJSON + "\"name\":\"" + devName + "\","; + HOMEdJSON = HOMEdJSON + "\"real\":true,"; + HOMEdJSON = HOMEdJSON + "\"exposes\": ["; + String options = ""; + for (JsonVariant value : arr) + { + String name = value["descr"]; + String device = selectToMarkerLast(value["topic"].as(), "/"); + // String id = ChipId + "-" + device; + String expose = value["name"]; + if (value["name"].as() == "toggle") + { + HOMEdJSON = HOMEdJSON + "\"switch_" + device + "\","; + } + else + { + HOMEdJSON = HOMEdJSON + "\"" + device + "\","; + } + + if (value["name"].as() == "anydataTmp") + { + // HOMEdJSON = HOMEdJSON + "\"temperature_" + device + "\","; + options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"temperature\", \"state\": \"measurement\", \"unit\": \"°C\", \"round\": 1},"; + } + if (value["name"].as() == "anydataHum") + { + // HOMEdJSON = HOMEdJSON + "\"humidity_" + device + "\","; + options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"humidity\", \"state\": \"measurement\", \"unit\": \"%\", \"round\": 1},"; + } + if (value["name"].as() == "inputDgt") + { + options = options + "\"" + device + "\":{\"type\": \"number\", \"min\": -10000, \"max\": 100000, \"step\": 0.1},"; + } + + i++; + } + options = options.substring(0, options.length() - 1); + HOMEdJSON = HOMEdJSON.substring(0, HOMEdJSON.length() - 1); + HOMEdJSON = HOMEdJSON + "],"; + HOMEdJSON = HOMEdJSON + " \"options\": {" + options + "}"; + HOMEdJSON = HOMEdJSON + "}}"; + String topic = (HOMEdTopic + "/command/custom").c_str(); + if (!publish(topic, HOMEdJSON)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to HOMEd")); + } + + file.close(); + + publishRetain(HOMEdTopic + "/device/custom/" + esp_id, "{\"status\":\"online\"}"); + + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->iAmLocal) + { + publishStatusMqtt((*it)->getID(), (*it)->getValue()); + (*it)->onMqttWsAppConnectEvent(); + } + } + } + } + void deleteFromHOMEd() + { + if (HOMEd) + { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if (*it) + { + String id_widget = (*it)->getID().c_str(); + String HOMEdjson = ""; + HOMEdjson = "{\"action\":\"removeDevice\","; + HOMEdjson = HOMEdjson + "\"device\":\""; + HOMEdjson = HOMEdjson + chipId; + HOMEdjson = HOMEdjson + "\"}"; + String topic = (HOMEdTopic + "/command/custom").c_str(); + if (!publish(topic, HOMEdjson)) + { + SerialPrint("E", F("MQTT"), F("Failed remove from HOMEd")); + } + } + } + } + } + IoTDiscovery *getHOMEdDiscovery() + { + if (HOMEd) + return this; + else + return nullptr; + } + ~DiscoveryHomeD(){}; +}; + +void *getAPI_DiscoveryHomeD(String subtype, String param) +{ + if (subtype == F("DiscoveryHomeD")) + { + return new DiscoveryHomeD(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/virtual/DiscoveryHomeD/modinfo.json b/src/modules/virtual/DiscoveryHomeD/modinfo.json new file mode 100644 index 00000000..a34e6f10 --- /dev/null +++ b/src/modules/virtual/DiscoveryHomeD/modinfo.json @@ -0,0 +1,41 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "Discovery of HomeD", + "type": "Reading", + "subtype": "DiscoveryHomeD", + "id": "homed", + "widget": "", + "page": "", + "descr": "", + "topic": "homed", + "names":1 + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "@AVAKS", + "moduleName": "DiscoveryHomeD", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "DiscoveryHomeD", + "moduleDesc": "Модуль проброса данных в MQTT для HOMEd-Custom", + "propInfo": { + "topic":"Топик службы HOMEd-Custom, например /myRoom/homed" + } + + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [], + "bk72*": [] + } +} \ No newline at end of file diff --git a/src/modules/virtual/Loging/Loging.cpp b/src/modules/virtual/Loging/Loging.cpp index 362fcdeb..9196c5cb 100644 --- a/src/modules/virtual/Loging/Loging.cpp +++ b/src/modules/virtual/Loging/Loging.cpp @@ -5,8 +5,9 @@ void *getAPI_Date(String params); -class Loging : public IoTItem { - private: +class Loging : public IoTItem +{ +private: String logid; String id; String tmpValue; @@ -14,6 +15,8 @@ class Loging : public IoTItem { int _publishType = -2; int _wsNum = -1; + int days = 1; + int daysShow = 1; int points; // int keepdays; @@ -25,45 +28,58 @@ class Loging : public IoTItem { // long interval; - public: - Loging(String parameters) : IoTItem(parameters) { +public: + Loging(String parameters) : IoTItem(parameters) + { jsonRead(parameters, F("logid"), logid); jsonRead(parameters, F("id"), id); jsonRead(parameters, F("points"), points); - if (points > 300) { + if (points > 300) + { points = 300; SerialPrint("E", F("Loging"), "'" + id + "' user set more points than allowed, value reset to 300"); } long interval; - jsonRead(parameters, F("int"), interval); // в минутах + jsonRead(parameters, F("int"), interval); // в минутах setInterval(interval * 60); // jsonRead(parameters, F("keepdays"), keepdays, false); + jsonRead(parameters, F("daysSave"), days); + days = days * 86400; + jsonRead(parameters, F("daysShow"), daysShow); + daysShow = daysShow * 86400; + // создадим экземпляр класса даты dateIoTItem = (IoTItem *)getAPI_Date("{\"id\": \"" + id + "-date\",\"int\":\"20\",\"subtype\":\"date\"}"); IoTItems.push_back(dateIoTItem); SerialPrint("I", F("Loging"), "created date instance " + id); } - void doByInterval() { + void doByInterval() + { // если объект логгирования не был создан - if (!isItemExist(logid)) { + if (!isItemExist(logid)) + { SerialPrint("E", F("Loging"), "'" + id + "' loging object not exist, return"); return; } String value = getItemValue(logid); // если значение логгирования пустое - if (value == "") { + if (value == "") + { SerialPrint("E", F("Loging"), "'" + id + "' loging value is empty, return"); return; } // если время не было получено из интернета - if (!isTimeSynch) { + if (!isTimeSynch) + { SerialPrint("E", F("Loging"), "'" + id + "' Сant loging - time not synchronized, return"); return; } + if (hasDayChanged()) + deleteOldFile(); regEvent(value, F("Loging")); @@ -76,13 +92,17 @@ class Loging : public IoTItem { String filePath = readDataDB(id); // если данные о файле отсутствуют, создадим новый - if (filePath == "failed" || filePath == "") { + if (filePath == "failed" || filePath == "") + { SerialPrint("E", F("Loging"), "'" + id + "' file path not found, start create new file"); createNewFileWithData(logData); return; - } else { + } + else + { // если файл все же есть но был создан не сегодня, то создаем сегодняшний - if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) { + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { SerialPrint("E", F("Loging"), "'" + id + "' file too old, start create new file"); createNewFileWithData(logData); return; @@ -95,29 +115,38 @@ class Loging : public IoTItem { SerialPrint("i", F("Loging"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); // если количество строк до заданной величины и дата не менялась - if (lines <= points && !hasDayChanged()) { + if (lines <= points && !hasDayChanged()) + { // просто добавим в существующий файл новые данные addNewDataToExistingFile(filePath, logData); // если больше или поменялась дата то создадим следующий файл - } else { + } + else + { createNewFileWithData(logData); } // запускаем процедуру удаления старых файлов если память переполняется deleteLastFile(); } - void SetDoByInterval(String valse) { + void SetDoByInterval(String valse) + { String value = valse; // если значение логгирования пустое - if (value == "") { + if (value == "") + { SerialPrint("E", F("LogingEvent"), "'" + id + "' loging value is empty, return"); return; } // если время не было получено из интернета - if (!isTimeSynch) { + if (!isTimeSynch) + { SerialPrint("E", F("LogingEvent"), "'" + id + "' Сant loging - time not synchronized, return"); return; } + if (hasDayChanged()) + deleteOldFile(); + regEvent(value, F("LogingEvent")); String logData; jsonWriteInt(logData, "x", unixTime, false); @@ -126,13 +155,17 @@ class Loging : public IoTItem { String filePath = readDataDB(id); // если данные о файле отсутствуют, создадим новый - if (filePath == "failed" || filePath == "") { + if (filePath == "failed" || filePath == "") + { SerialPrint("E", F("LogingEvent"), "'" + id + "' file path not found, start create new file"); createNewFileWithData(logData); return; - } else { + } + else + { // если файл все же есть но был создан не сегодня, то создаем сегодняшний - if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) { + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { SerialPrint("E", F("LogingEvent"), "'" + id + "' file too old, start create new file"); createNewFileWithData(logData); return; @@ -145,53 +178,73 @@ class Loging : public IoTItem { SerialPrint("i", F("LogingEvent"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); // если количество строк до заданной величины и дата не менялась - if (lines <= points && !hasDayChanged()) { + if (lines <= points && !hasDayChanged()) + { // просто добавим в существующий файл новые данные addNewDataToExistingFile(filePath, logData); // если больше или поменялась дата то создадим следующий файл - } else { + } + else + { createNewFileWithData(logData); } // запускаем процедуру удаления старых файлов если память переполняется deleteLastFile(); } - void createNewFileWithData(String &logData) { + void createNewFileWithData(String &logData) + { logData = logData + ","; - String path = "/lg/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt + String path = "/lg/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt // создадим пустой файл - if (writeEmptyFile(path) != "success") { + if (writeEmptyFile(path) != "success") + { SerialPrint("E", F("Loging"), "'" + id + "' file writing error, return"); return; } // запишем в него данные - if (addFile(path, logData) != "success") { + if (addFile(path, logData) != "success") + { SerialPrint("E", F("Loging"), "'" + id + "' data writing error, return"); return; } // запишем путь к нему в базу данных - if (saveDataDB(id, path) != "success") { + if (saveDataDB(id, path) != "success") + { SerialPrint("E", F("Loging"), "'" + id + "' db file writing error, return"); return; } +#ifdef LIBRETINY + SerialPrint("i", F("Loging"), "'" + id + "' file created http://" + ipToString(WiFi.localIP()) + path); +#else SerialPrint("i", F("Loging"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); +#endif } - void addNewDataToExistingFile(String &path, String &logData) { + void addNewDataToExistingFile(String &path, String &logData) + { logData = logData + ","; - if (addFile(path, logData) != "success") { + if (addFile(path, logData) != "success") + { SerialPrint("i", F("Loging"), "'" + id + "' file writing error, return"); return; }; +#ifdef LIBRETINY + SerialPrint("i", F("Loging"), "'" + id + "' loging in file http://" + ipToString(WiFi.localIP()) + path); +#else SerialPrint("i", F("Loging"), "'" + id + "' loging in file http://" + WiFi.localIP().toString() + path); +#endif } // данная функция уже перенесена в ядро и будет удалена в последствии - bool hasDayChanged() { + bool hasDayChanged() + { bool changed = false; String currentDate = getTodayDateDotFormated(); - if (!firstTimeInit) { - if (prevDate != currentDate) { + if (!firstTimeInit) + { + if (prevDate != currentDate) + { changed = true; SerialPrint("i", F("NTP"), F("Change day event")); #if defined(ESP8266) @@ -206,7 +259,8 @@ class Loging : public IoTItem { return changed; } - void publishValue() { + void publishValue() + { String dir = "/lg/" + id; filesList = getFilesList(dir); @@ -216,75 +270,111 @@ class Loging : public IoTItem { bool noData = true; - while (filesList.length()) { + while (filesList.length()) + { String path = selectToMarker(filesList, ";"); - - path = "/lg/" + id + path; + path = dir + path; f++; unsigned long fileUnixTimeLocal = getFileUnixLocalTime(path); unsigned long reqUnixTime = strDateToUnix(getItemValue(id + "-date")); - if (fileUnixTimeLocal > reqUnixTime && fileUnixTimeLocal < reqUnixTime + 86400) { + + if (fileUnixTimeLocal > reqUnixTime - daysShow && fileUnixTimeLocal < reqUnixTime + 86400) + { noData = false; String json = getAdditionalJson(); - if (_publishType == TO_MQTT) { + if (_publishType == TO_MQTT) + { publishChartFileToMqtt(path, id, calculateMaxCount()); - } else if (_publishType == TO_WS) { + } + else if (_publishType == TO_WS) + { sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); - } else if (_publishType == TO_MQTT_WS) { + } + else if (_publishType == TO_MQTT_WS) + { sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); publishChartFileToMqtt(path, id, calculateMaxCount()); } SerialPrint("i", F("Loging"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", sent"); - } else { + } + else + { SerialPrint("i", F("Loging"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", skipped"); } + /* + //------------ delete old files ---------------- + String pathTodel = path; + pathTodel.replace("/", ""); + pathTodel.replace(".txt", ""); + int pathTodel_ = pathTodel.toInt(); + if (pathTodel_ < unixTimeShort - days) + { + removeFile(path); + SerialPrint("i", "Loging!!!!!!", path + " => old files been clean"); + } + if (pathTodel_ < unixTimeShort - 5184000) + { + removeFile(path); + SerialPrint("i", "Loging!!!!!!", path + " => > 2 month files been clean"); + } + //------------ delete old files ---------------- + */ filesList = deleteBeforeDelimiter(filesList, ";"); } // если данных нет отправляем пустой грфик - if (noData) { + if (noData) + { clearValue(); } } - String getAdditionalJson() { + String getAdditionalJson() + { String topic = mqttRootDevice + "/" + id; String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; return json; } - void publishChartToWsSinglePoint(String value) { + void publishChartToWsSinglePoint(String value) + { String topic = mqttRootDevice + "/" + id; String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + "}]}"; sendStringToWs("chartb", json, -1); } - void clearValue() { + void clearValue() + { String topic = mqttRootDevice + "/" + id; String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}"; sendStringToWs("chartb", json, -1); } - void clearHistory() { + void clearHistory() + { String dir = "/lg/" + id; cleanDirectory(dir); } - void deleteLastFile() { + void deleteLastFile() + { IoTFSInfo tmp = getFSInfo(); SerialPrint("i", "Loging", String(tmp.freePer) + " % free flash remaining"); - if (tmp.freePer <= 50.00) { + if (tmp.freePer <= 10.00) + { String dir = "/lg/" + id; filesList = getFilesList(dir); int i = 0; - while (filesList.length()) { + while (filesList.length()) + { String path = selectToMarker(filesList, ";"); path = dir + path; i++; - if (i == 1) { + if (i == 1) + { removeFile(path); SerialPrint("!", "Loging", String(i) + ") " + path + " => oldest files been deleted"); return; @@ -295,7 +385,37 @@ class Loging : public IoTItem { } } - void setPublishDestination(int publishType, int wsNum) { + void deleteOldFile() + { + String dir = "/lg/" + id; + filesList = getFilesList(dir); + int i = 0; + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + String pathTodel = path; + pathTodel.replace("/", ""); + pathTodel.replace(".txt", ""); + int pathTodel_ = pathTodel.toInt(); + path = dir + path; + i++; + if (pathTodel_ < unixTimeShort - days) + { + removeFile(path); + SerialPrint("i", "Loging!!!!!!", String(i) + ") " + path + " => old files been clean"); + } + if (pathTodel_ < unixTimeShort - 5184000) + { + removeFile(path); + SerialPrint("i", "Loging!!!!!!", String(i) + ") " + path + " => > 2 month files been clean"); + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + } + + void setPublishDestination(int publishType, int wsNum) + { _publishType = publishType; _wsNum = wsNum; } @@ -315,11 +435,13 @@ class Loging : public IoTItem { // } // } - void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true) { + void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true) + { String userDate = getItemValue(id + "-date"); String currentDate = getTodayDateDotFormated(); // отправляем в график данные только когда выбран сегодняшний день - if (userDate == currentDate) { + if (userDate == currentDate) + { // generateEvent(_id, value); // publishStatusMqtt(_id, value); @@ -333,7 +455,8 @@ class Loging : public IoTItem { // путь вида: /lg/log/1231231.txt unsigned long getFileUnixLocalTime(String path) { return gmtTimeToLocal(selectToMarkerLast(deleteToMarkerLast(path, "."), "/").toInt() + START_DATETIME); } - void setValue(const IoTValue &Value, bool genEvent = true) { + void setValue(const IoTValue &Value, bool genEvent = true) + { value = Value; this->SetDoByInterval(String(value.valD)); SerialPrint("i", "Loging", "setValue:" + String(value.valD)); @@ -341,37 +464,48 @@ class Loging : public IoTItem { } }; -void *getAPI_Loging(String subtype, String param) { - if (subtype == F("Loging")) { +void *getAPI_Loging(String subtype, String param) +{ + if (subtype == F("Loging")) + { return new Loging(param); - } else { + } + else + { return nullptr; } } -class Date : public IoTItem { - private: +class Date : public IoTItem +{ +private: bool firstTime = true; - public: +public: String id; - Date(String parameters) : IoTItem(parameters) { + Date(String parameters) : IoTItem(parameters) + { jsonRead(parameters, F("id"), id); value.isDecimal = false; } - void setValue(const String &valStr, bool genEvent = true) { + void setValue(const String &valStr, bool genEvent = true) + { value.valS = valStr; setValue(value, genEvent); } - void setValue(const IoTValue &Value, bool genEvent = true) { + void setValue(const IoTValue &Value, bool genEvent = true) + { value = Value; regEvent(value.valS, "", false, genEvent); // отправка данных при изменении даты - for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { - if ((*it)->getSubtype() == "Loging") { - if ((*it)->getID() == selectToMarker(id, "-")) { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->getSubtype() == "Loging") + { + if ((*it)->getID() == selectToMarker(id, "-")) + { (*it)->setPublishDestination(TO_MQTT_WS, -1); (*it)->publishValue(); } @@ -379,14 +513,18 @@ class Date : public IoTItem { } } - void setTodayDate() { + void setTodayDate() + { setValue(getTodayDateDotFormated()); - SerialPrint("E", F("Loging"), "today date set " + getTodayDateDotFormated()); + SerialPrint("i", F("Loging"), "today date set " + getTodayDateDotFormated()); } - void doByInterval() { - if (isTimeSynch) { - if (firstTime) { + void doByInterval() + { + if (isTimeSynch) + { + if (firstTime) + { setTodayDate(); firstTime = false; } diff --git a/src/modules/virtual/Loging/modinfo.json b/src/modules/virtual/Loging/modinfo.json index 61b2ad2d..6a0a4b98 100644 --- a/src/modules/virtual/Loging/modinfo.json +++ b/src/modules/virtual/Loging/modinfo.json @@ -1,56 +1,63 @@ { - "menuSection": "virtual_elments", - "configItem": [ - { - "global": 0, - "name": "График", - "type": "Writing", - "subtype": "Loging", - "id": "log", - "widget": "chart2", - "page": "Графики", - "descr": "Температура", - "num": 1, - "int": 5, - "logid": "t", - "points": 300 - }, - { - "global": 0, - "name": "График по событию", - "type": "Writing", - "subtype": "Loging", - "id": "log", - "widget": "chart2", - "page": "Графики", - "descr": "Температура", - "int": 0, - "num": 1, - "points": 300 - } - ], - "about": { - "authorName": "Dmitry Borisenko", - "authorContact": "https://t.me/Dmitry_Borisenko", - "authorGit": "https://github.com/DmitryBorisenko33", - "specialThanks": "@itsid1 @Valiuhaaa Serg", - "moduleName": "Loging", - "moduleVersion": "3.0", - "usedRam": { - "esp32_4mb": 15, - "esp8266_4mb": 15 - }, - "title": "Логирование в график", - "moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов", - "propInfo": { - "int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут", - "logid": "ID величины которую будем логировать", - "points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр" - } + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "График", + "type": "Writing", + "subtype": "Loging", + "id": "log", + "widget": "chart2", + "page": "Графики", + "descr": "Температура", + "num": 1, + "int": 5, + "logid": "t", + "daysSave": 5, + "daysShow": 0, + "points": 300 }, - "defActive": true, - "usedLibs": { - "esp32*": [], - "esp82*": [] + { + "global": 0, + "name": "График по событию", + "type": "Writing", + "subtype": "Loging", + "id": "log", + "widget": "chart2", + "page": "Графики", + "descr": "Температура", + "int": 0, + "num": 1, + "daysSave": 5, + "daysShow": 0, + "points": 300 } + ], + "about": { + "authorName": "Dmitry Borisenko", + "authorContact": "https://t.me/Dmitry_Borisenko", + "authorGit": "https://github.com/DmitryBorisenko33", + "specialThanks": "@itsid1 @Valiuhaaa Serg", + "moduleName": "Loging", + "moduleVersion": "4.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Логирование в график", + "moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов", + "propInfo": { + "int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут", + "logid": "ID величины которую будем логировать", + "points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр", + "daysSave": "Количество дней за которое надо хранить график", + "daysShow": "Какое количество дней отображать на графике, в дополнение к текущему. 0 - за текущие сутки с ноля часов; 1 - вчерашние сутки и сегодняшние, и т.д." + } + }, + "defActive": true, + "usedLibs": { + "esp32*": [], + "esp82*": [], + "bk72*": [] + } } \ No newline at end of file diff --git a/src/modules/virtual/Loging2/Loging2.cpp b/src/modules/virtual/Loging2/Loging2.cpp new file mode 100644 index 00000000..3749bbb8 --- /dev/null +++ b/src/modules/virtual/Loging2/Loging2.cpp @@ -0,0 +1,495 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPConfiguration.h" +#include "NTP.h" + +void *getAPI_Date2(String params); + +class Loging2 : public IoTItem +{ +private: + String logid1; + String logid2; + String id; + String tmpValue; + String filesList = ""; + + int _publishType = -2; + int _wsNum = -1; + + int points; + // int keepdays; + + IoTItem *dateIoTItem; + + String prevDate = ""; + bool firstTimeInit = true; + + // long interval; + +public: + Loging2(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("logid1"), logid1); + jsonRead(parameters, F("logid2"), logid2); + jsonRead(parameters, F("id"), id); + jsonRead(parameters, F("points"), points); + if (points > 300) + { + points = 300; + SerialPrint("E", F("Loging2"), "'" + id + "' user set more points than allowed, value reset to 300"); + } + long interval; + jsonRead(parameters, F("int"), interval); // в минутах + setInterval(interval * 60); + // jsonRead(parameters, F("keepdays"), keepdays, false); + + // создадим экземпляр класса даты + dateIoTItem = (IoTItem *)getAPI_Date2("{\"id\": \"" + id + "-date\",\"int\":\"20\",\"subtype\":\"date\"}"); + IoTItems.push_back(dateIoTItem); + SerialPrint("I", F("Loging2"), "created date instance " + id); + } + + void doByInterval() + { + // если объект логгирования не был создан + if (!isItemExist(logid1)) + { + SerialPrint("E", F("Loging2"), "'" + id + "' loging object not exist, return"); + return; + } + + String value = getItemValue(logid1); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging2"), "'" + id + "' loging value is empty, return"); + return; + } + String value2 = getItemValue(logid2); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging2"), "'" + id + "' loging value is empty, return"); + return; + } + + // если время не было получено из интернета + if (!isTimeSynch) + { + SerialPrint("E", F("Loging2"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + + regEvent(value, F("Loging2")); + + String logData2; + + jsonWriteInt(logData2, "x", unixTime, false); + jsonWriteFloat(logData2, "y1", value.toFloat(), false); + jsonWriteFloat(logData2, "y2", value2.toFloat(), false); + + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") + { + SerialPrint("E", F("Loging2"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData2); + return; + } + else + { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { + SerialPrint("E", F("Loging2"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData2); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging2"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) + { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData2); + // если больше или поменялась дата то создадим следующий файл + } + else + { + createNewFileWithData(logData2); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } +/* + void SetDoByInterval(String valse) + { + String value = valse; + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' loging value is empty, return"); + return; + } + // если время не было получено из интернета + if (!isTimeSynch) + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + regEvent(value, F("Loging2Event")); + String logData2; + jsonWriteInt(logData2, "x", unixTime, false); + jsonWriteFloat(logData2, "y1", value.toFloat(), false); + jsonWriteFloat(logData2, "y2", value.toFloat(), false); + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData2); + return; + } + else + { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { + SerialPrint("E", F("Loging2Event"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData2); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging2Event"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) + { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData2); + // если больше или поменялась дата то создадим следующий файл + } + else + { + createNewFileWithData(logData2); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } +*/ + void createNewFileWithData(String &logData) + { + logData = logData + ","; + String path = "/lg2/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt + // создадим пустой файл + if (writeEmptyFile(path) != "success") + { + SerialPrint("E", F("Loging2"), "'" + id + "' file writing error, return"); + return; + } + + // запишем в него данные + if (addFile(path, logData) != "success") + { + SerialPrint("E", F("Loging2"), "'" + id + "' data writing error, return"); + return; + } + // запишем путь к нему в базу данных + if (saveDataDB(id, path) != "success") + { + SerialPrint("E", F("Loging2"), "'" + id + "' db file writing error, return"); + return; + } + SerialPrint("i", F("Loging2"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); + } + + void addNewDataToExistingFile(String &path, String &logData) + { + logData = logData + ","; + if (addFile(path, logData) != "success") + { + SerialPrint("i", F("Loging2"), "'" + id + "' file writing error, return"); + return; + }; + SerialPrint("i", F("Loging2"), "'" + id + "' loging in file http://" + WiFi.localIP().toString() + path); + } + + // данная функция уже перенесена в ядро и будет удалена в последствии + bool hasDayChanged() + { + bool changed = false; + String currentDate = getTodayDateDotFormated(); + if (!firstTimeInit) + { + if (prevDate != currentDate) + { + changed = true; + SerialPrint("i", F("NTP"), F("Change day event")); +#if defined(ESP8266) + FileFS.gc(); +#endif +#if defined(ESP32) +#endif + } + } + firstTimeInit = false; + prevDate = currentDate; + return changed; + } + + void publishValue() + { + String dir = "/lg2/" + id; + filesList = getFilesList(dir); + + SerialPrint("i", F("Loging2"), "file list: " + filesList); + + int f = 0; + + bool noData = true; + + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + + path = "/lg2/" + id + path; + + f++; + + unsigned long fileUnixTimeLocal = getFileUnixLocalTime(path); + + unsigned long reqUnixTime = strDateToUnix(getItemValue(id + "-date")); + if (fileUnixTimeLocal > reqUnixTime && fileUnixTimeLocal < reqUnixTime + 86400) + { + noData = false; + String json = getAdditionalJson(); + if (_publishType == TO_MQTT) + { + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + else if (_publishType == TO_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + } + else if (_publishType == TO_MQTT_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + SerialPrint("i", F("Loging2"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", sent"); + } + else + { + SerialPrint("i", F("Loging2"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", skipped"); + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + // если данных нет отправляем пустой грфик + if (noData) + { + clearValue(); + } + } + + String getAdditionalJson() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; + return json; + } + + void publishChartToWsSinglePoint(String value) + { + String topic = mqttRootDevice + "/" + id; + String value2 = getItemValue(logid2); + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + ",\"y2\":" + value2 + "}]}"; + sendStringToWs("chartb", json, -1); + } + + void clearValue() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}"; + sendStringToWs("chartb", json, -1); + } + + void clearHistory() + { + String dir = "/lg2/" + id; + cleanDirectory(dir); + } + + void deleteLastFile() + { + IoTFSInfo tmp = getFSInfo(); + SerialPrint("i", "Loging2", String(tmp.freePer) + " % free flash remaining"); + if (tmp.freePer <= 20.00) + { + String dir = "/lg/" + id; + filesList = getFilesList(dir); + int i = 0; + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + path = dir + path; + i++; + if (i == 1) + { + removeFile(path); + SerialPrint("!", "Loging2", String(i) + ") " + path + " => oldest files been deleted"); + return; + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + } + } + + void setPublishDestination(int publishType, int wsNum) + { + _publishType = publishType; + _wsNum = wsNum; + } + + String getValue() + { + return ""; + } + /* + void loop() { + if (enableDoByInt) { + currentMillis = millis(); + difference = currentMillis - prevMillis; + if (difference >= interval) { + prevMillis = millis(); + if (interval != 0) { + this->doByInterval(); + } + } + } + } + */ + void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true) + { + String userDate = getItemValue(id + "-date"); + String currentDate = getTodayDateDotFormated(); + // отправляем в график данные только когда выбран сегодняшний день + if (userDate == currentDate) + { + // generateEvent(_id, value); + // publishStatusMqtt(_id, value); + + publishChartToWsSinglePoint(value); + // SerialPrint("i", "Sensor " + consoleInfo, "'" + _id + "' data: " + value + "'"); + } + } + + // просто максимальное количество точек + int calculateMaxCount() + { + return 86400; + } + + // путь вида: /lg/log/1231231.txt + unsigned long getFileUnixLocalTime(String path) + { + return gmtTimeToLocal(selectToMarkerLast(deleteToMarkerLast(path, "."), "/").toInt() + START_DATETIME); + } + /* + void setValue(const IoTValue &Value, bool genEvent = true) + { + value = Value; + this->SetDoByInterval(String(value.valD)); + SerialPrint("i", "Loging2", "setValue:" + String(value.valD)); + regEvent(value.valS, "Loging2", false, genEvent); + } +*/ +}; + +void *getAPI_Loging2(String subtype, String param) +{ + if (subtype == F("Loging2")) + { + return new Loging2(param); + } + else + { + return nullptr; + } +} + +class Date : public IoTItem +{ +private: + bool firstTime = true; + +public: + String id; + Date(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("id"), id); + value.isDecimal = false; + } + + void setValue(const String &valStr, bool genEvent = true) + { + value.valS = valStr; + setValue(value, genEvent); + } + + void setValue(const IoTValue &Value, bool genEvent = true) + { + value = Value; + regEvent(value.valS, "", false, genEvent); + // отправка данных при изменении даты + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->getSubtype() == "Loging2") + { + if ((*it)->getID() == selectToMarker(id, "-")) + { + (*it)->setPublishDestination(TO_MQTT_WS, -1); + (*it)->publishValue(); + } + } + } + } + + void setTodayDate() + { + setValue(getTodayDateDotFormated()); + SerialPrint("E", F("Loging2"), "today date set " + getTodayDateDotFormated()); + } + + void doByInterval() + { + if (isTimeSynch) + { + if (firstTime) + { + setTodayDate(); + firstTime = false; + } + } + } +}; + +void *getAPI_Date2(String param) +{ + return new Date(param); +} diff --git a/src/modules/virtual/Loging2/modinfo.json b/src/modules/virtual/Loging2/modinfo.json new file mode 100644 index 00000000..0841e795 --- /dev/null +++ b/src/modules/virtual/Loging2/modinfo.json @@ -0,0 +1,48 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "Двойной график", + "type": "Writing", + "subtype": "Loging2", + "id": "log2", + "widget": "chart5", + "page": "Графики", + "descr": "Датчик", + "num": 1, + "int": 5, + "logid1": "t", + "logid2": "h", + "points": 300, + "series1": "Температура, С", + "series2": "Влажность, %" + } + ], + "about": { + "authorName": "Serghei Crasnicov", + "authorContact": "https://t.me/Serghei63", + "authorGit": "https://github.com/Serghei63", + "specialThanks": "@itsid1 @Valiuhaaa Serg", + "moduleName": "Loging2", + "moduleVersion": "0.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Логирование в график", + "moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов", + "propInfo": { + "int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут", + "logid1": "ID 1 величины которую будем логировать (температура)", + "logid2": "ID 2 величины которую будем логировать (влажность)", + "points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр" + } + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [] + + } +} \ No newline at end of file diff --git a/src/modules/virtual/Loging3/Loging3.cpp b/src/modules/virtual/Loging3/Loging3.cpp new file mode 100644 index 00000000..fa9254f3 --- /dev/null +++ b/src/modules/virtual/Loging3/Loging3.cpp @@ -0,0 +1,498 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPConfiguration.h" +#include "NTP.h" + +void *getAPI_Date3(String params); + +class Loging3 : public IoTItem +{ +private: + String logid1; + String logid2; + String logid3; + String id; + String tmpValue; + String filesList = ""; + + int _publishType = -2; + int _wsNum = -1; + + int points; + // int keepdays; + + IoTItem *dateIoTItem; + + String prevDate = ""; + bool firstTimeInit = true; + + // long interval; + +public: + Loging3(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("logid1"), logid1); + jsonRead(parameters, F("logid2"), logid2); + jsonRead(parameters, F("logid3"), logid3); + jsonRead(parameters, F("id"), id); + jsonRead(parameters, F("points"), points); + if (points > 300) + { + points = 300; + SerialPrint("E", F("Loging3"), "'" + id + "' user set more points than allowed, value reset to 300"); + } + + long interval; + jsonRead(parameters, F("int"), interval); // в минутах + setInterval(interval * 60); + // jsonRead(parameters, F("keepdays"), keepdays, false); + + // создадим экземпляр класса даты + dateIoTItem = (IoTItem *)getAPI_Date3("{\"id\": \"" + id + "-date\",\"int\":\"20\",\"subtype\":\"date\"}"); + IoTItems.push_back(dateIoTItem); + SerialPrint("I", F("Loging3"), "created date instance " + id); + } + + void doByInterval() + { + // если объект логгирования не был создан + if (!isItemExist(logid1)) + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging object not exist, return"); + return; + } + + String value = getItemValue(logid1); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging value is empty, return"); + return; + } + String value2 = getItemValue(logid2); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging value is empty, return"); + return; + } + String value3 = getItemValue(logid3); + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' loging value is empty, return"); + return; + } + + // если время не было получено из интернета + if (!isTimeSynch) + { + SerialPrint("E", F("Loging"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + + regEvent(value, F("Loging3")); + + // String logData2; + String logData3; + + jsonWriteInt(logData3, "x", unixTime, false); + jsonWriteFloat(logData3, "y1", value.toFloat(), false); + jsonWriteFloat(logData3, "y2", value2.toFloat(), false); + jsonWriteFloat(logData3, "y3", value3.toFloat(), false); + + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") + { + SerialPrint("E", F("Loging3"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData3); + return; + } + else + { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) + { + SerialPrint("E", F("Loging3"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData3); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging3"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) + { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData3); + // если больше или поменялась дата то создадим следующий файл + } + else + { + createNewFileWithData(logData3); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } + /* + void SetDoByInterval(String valse) { + String value = valse; + // если значение логгирования пустое + if (value == "") { + SerialPrint("E", F("Loging3Event"), "'" + id + "' loging value is empty, return"); + return; + } + // если время не было получено из интернета + if (!isTimeSynch) { + SerialPrint("E", F("Loging3Event"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + regEvent(value, F("Loging3Event")); + String logData3; + jsonWriteInt(logData3, "x", unixTime, false); + jsonWriteFloat(logData3, "y1", value.toFloat(), false); + jsonWriteFloat(logData3, "y2", value.toFloat(), false); + jsonWriteFloat(logData3, "y3", value.toFloat(), false); + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") { + SerialPrint("E", F("Loging3Event"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData3); + return; + } else { + // если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) { + SerialPrint("E", F("Loging3Event"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData3); + return; + } + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("Loging3Event"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData3); + // если больше или поменялась дата то создадим следующий файл + } else { + createNewFileWithData(logData3); + } + // запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + } + */ + void createNewFileWithData(String &logData) + { + logData = logData + ","; + String path = "/lg3/" + id + "/" + String(unixTimeShort) + ".txt"; // создадим путь вида /lg/id/133256622333.txt + // создадим пустой файл + if (writeEmptyFile(path) != "success") + { + SerialPrint("E", F("Loging"), "'" + id + "' file writing error, return"); + return; + } + + // запишем в него данные + if (addFile(path, logData) != "success") + { + SerialPrint("E", F("Loging3"), "'" + id + "' data writing error, return"); + return; + } + // запишем путь к нему в базу данных + if (saveDataDB(id, path) != "success") + { + SerialPrint("E", F("Loging3"), "'" + id + "' db file writing error, return"); + return; + } + SerialPrint("i", F("Loging3"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); + } + + void addNewDataToExistingFile(String &path, String &logData) + { + logData = logData + ","; + if (addFile(path, logData) != "success") + { + SerialPrint("i", F("Loging3"), "'" + id + "' file writing error, return"); + return; + }; + SerialPrint("i", F("Loging3"), "'" + id + "' loging in file http://" + WiFi.localIP().toString() + path); + } + + // данная функция уже перенесена в ядро и будет удалена в последствии + bool hasDayChanged() + { + bool changed = false; + String currentDate = getTodayDateDotFormated(); + if (!firstTimeInit) + { + if (prevDate != currentDate) + { + changed = true; + SerialPrint("i", F("NTP"), F("Change day event")); +#if defined(ESP8266) + FileFS.gc(); +#endif +#if defined(ESP32) +#endif + } + } + firstTimeInit = false; + prevDate = currentDate; + return changed; + } + + void publishValue() + { + String dir = "/lg3/" + id; + filesList = getFilesList(dir); + + SerialPrint("i", F("Loging3"), "file list: " + filesList); + + int f = 0; + + bool noData = true; + + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + + path = "/lg3/" + id + path; + + f++; + + unsigned long fileUnixTimeLocal = getFileUnixLocalTime(path); + + unsigned long reqUnixTime = strDateToUnix(getItemValue(id + "-date")); + if (fileUnixTimeLocal > reqUnixTime && fileUnixTimeLocal < reqUnixTime + 86400) + { + noData = false; + String json = getAdditionalJson(); + if (_publishType == TO_MQTT) + { + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + else if (_publishType == TO_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + } + else if (_publishType == TO_MQTT_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + SerialPrint("i", F("Loging3"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", sent"); + } + else + { + SerialPrint("i", F("Loging3"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", skipped"); + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + // если данных нет отправляем пустой грфик + if (noData) + { + clearValue(); + } + } + + String getAdditionalJson() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; + return json; + } + + void publishChartToWsSinglePoint(String value) + { + String topic = mqttRootDevice + "/" + id; + String value2 = getItemValue(logid2); + String value3 = getItemValue(logid3); + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + ",\"y2\":" + value2 + ",\"y3\":" + value3 + "}]}"; + sendStringToWs("chartb", json, -1); + } + + void clearValue() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}"; + sendStringToWs("chartb", json, -1); + } + + void clearHistory() + { + String dir = "/lg3/" + id; + cleanDirectory(dir); + } + + void deleteLastFile() + { + IoTFSInfo tmp = getFSInfo(); + SerialPrint("i", "Loging3", String(tmp.freePer) + " % free flash remaining"); + if (tmp.freePer <= 20.00) + { + String dir = "/lg3/" + id; + filesList = getFilesList(dir); + int i = 0; + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + path = dir + path; + i++; + if (i == 1) + { + removeFile(path); + SerialPrint("!", "Loging3", String(i) + ") " + path + " => oldest files been deleted"); + return; + } + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + } + } + + void setPublishDestination(int publishType, int wsNum) + { + _publishType = publishType; + _wsNum = wsNum; + } + + String getValue() + { + return ""; + } + /* + void loop() { + if (enableDoByInt) { + currentMillis = millis(); + difference = currentMillis - prevMillis; + if (difference >= interval) { + prevMillis = millis(); + if (interval != 0) { + this->doByInterval(); + } + } + } + } + */ + void regEvent(const String &value, const String &consoleInfo, bool error = false, bool genEvent = true) + { + String userDate = getItemValue(id + "-date"); + String currentDate = getTodayDateDotFormated(); + // отправляем в график данные только когда выбран сегодняшний день + if (userDate == currentDate) + { + // generateEvent(_id, value); + // publishStatusMqtt(_id, value); + + publishChartToWsSinglePoint(value); + // SerialPrint("i", "Sensor " + consoleInfo, "'" + _id + "' data: " + value + "'"); + } + } + + // просто максимальное количество точек + int calculateMaxCount() + { + return 86400; + } + + // путь вида: /lg/log/1231231.txt + unsigned long getFileUnixLocalTime(String path) + { + return gmtTimeToLocal(selectToMarkerLast(deleteToMarkerLast(path, "."), "/").toInt() + START_DATETIME); + } + /* + void setValue(const IoTValue &Value, bool genEvent = true) { + value = Value; + this->SetDoByInterval(String(value.valD)); + SerialPrint("i", "Loging3", "setValue:" + String(value.valD)); + regEvent(value.valS, "Loging3", false, genEvent); + } + */ +}; + +void *getAPI_Loging3(String subtype, String param) +{ + if (subtype == F("Loging3")) + { + return new Loging3(param); + } + else + { + return nullptr; + } +} + +class Date : public IoTItem +{ +private: + bool firstTime = true; + +public: + String id; + Date(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("id"), id); + value.isDecimal = false; + } + + void setValue(const String &valStr, bool genEvent = true) + { + value.valS = valStr; + setValue(value, genEvent); + } + + void setValue(const IoTValue &Value, bool genEvent = true) + { + value = Value; + regEvent(value.valS, "", false, genEvent); + // отправка данных при изменении даты + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->getSubtype() == "Loging3") + { + if ((*it)->getID() == selectToMarker(id, "-")) + { + (*it)->setPublishDestination(TO_MQTT_WS, -1); + (*it)->publishValue(); + } + } + } + } + + void setTodayDate() + { + setValue(getTodayDateDotFormated()); + SerialPrint("E", F("Loging3"), "today date set " + getTodayDateDotFormated()); + } + + void doByInterval() + { + if (isTimeSynch) + { + if (firstTime) + { + setTodayDate(); + firstTime = false; + } + } + } +}; + +void *getAPI_Date3(String param) +{ + return new Date(param); +} diff --git a/src/modules/virtual/Loging3/modinfo.json b/src/modules/virtual/Loging3/modinfo.json new file mode 100644 index 00000000..11a9420d --- /dev/null +++ b/src/modules/virtual/Loging3/modinfo.json @@ -0,0 +1,51 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "Тройной график", + "type": "Writing", + "subtype": "Loging3", + "id": "log3", + "widget": "chart6", + "page": "Графики", + "descr": "Датчик", + "num": 1, + "int": 5, + "logid1": "t", + "logid2": "h", + "logid3": "p", + "points": 300, + "series1": "Температура, С", + "series2": "Влажность, %", + "series3": "Давление, кПа" + } + ], + "about": { + "authorName": "Serghei Crasnicov", + "authorContact": "https://t.me/Serghei63", + "authorGit": "https://github.com/Serghei63", + "specialThanks": "@itsid1 @Valiuhaaa Serg", + "moduleName": "Loging3", + "moduleVersion": "0.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Логирование в график", + "moduleDesc": "Расширение позволяющее логировать любую величину в график. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp. В окне ввода даты можно выбирать день, историю которого вы хотите посмотреть. Старые файлы будут удаляться автоматически после того как объем оставшейся flesh памяти устройства будет менее 20 процентов", + "propInfo": { + "int": "Интервал логирования в мнутах, рекомендуется для esp8266 использоать интервал не менее 5-ти минут", + "logid1": "ID 1 величины которую будем логировать (температура)", + "logid2": "ID 2 величины которую будем логировать (влажность)", + "logid3": "ID 3 величины которую будем логировать (давление)", + "points": "Максимальное количество точек в одном файле, может быть не более 300. Не рекомендуется менять этот параметр" + } + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [] + + } +} \ No newline at end of file diff --git a/src/modules/virtual/LogingDaily/LogingDaily.cpp b/src/modules/virtual/LogingDaily/LogingDaily.cpp index b6f5b1e0..29926cf9 100644 --- a/src/modules/virtual/LogingDaily/LogingDaily.cpp +++ b/src/modules/virtual/LogingDaily/LogingDaily.cpp @@ -139,7 +139,11 @@ class LogingDaily : public IoTItem { SerialPrint("E", F("LogingDaily"), "'" + id + "' db file writing error, return"); return; } + #ifdef LIBRETINY + SerialPrint("i", F("LogingDaily"), "'" + id + "' file created http://" + ipToString(WiFi.localIP()) + path); + #else SerialPrint("i", F("LogingDaily"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); + #endif } void addNewDataToExistingFile(String &path, String &logData) { @@ -148,7 +152,11 @@ class LogingDaily : public IoTItem { SerialPrint("i", F("LogingDaily"), "'" + id + "' file writing error, return"); return; }; + #ifdef LIBRETINY + SerialPrint("i", F("LogingDaily"), "'" + id + "' LogingDaily in file http://" + ipToString(WiFi.localIP()) + path); + #else SerialPrint("i", F("LogingDaily"), "'" + id + "' LogingDaily in file http://" + WiFi.localIP().toString() + path); + #endif } bool hasDayChanged() { diff --git a/src/modules/virtual/LogingDaily/modinfo.json b/src/modules/virtual/LogingDaily/modinfo.json index 1bfbc10d..3fd3e1ea 100644 --- a/src/modules/virtual/LogingDaily/modinfo.json +++ b/src/modules/virtual/LogingDaily/modinfo.json @@ -1,49 +1,50 @@ { - "menuSection": "virtual_elments", - "configItem": [ - { - "global": 0, - "name": "График дневного расхода", - "type": "Writing", - "subtype": "LogingDaily", - "id": "log", - "widget": "chart3", - "page": "Графики", - "descr": "Температура", - "num": 1, - "int": 1, - "logid": "t", - "points": 200, - "telegram": 0, - "test": 0, - "btn-defvalue": 0, - "btn-reset": "nil" - } - ], - "about": { - "authorName": "Dmitry Borisenko", - "authorContact": "https://t.me/Dmitry_Borisenko", - "authorGit": "https://github.com/DmitryBorisenko33", - "specialThanks": "@itsid1 @Valiuhaaa Serg", - "moduleName": "LogingDaily", - "moduleVersion": "3.1", - "usedRam": { - "esp32_4mb": 15, - "esp8266_4mb": 15 - }, - "title": "График дневного расхода", - "moduleDesc": "Расширение позволяющее логировать накопительные величины и видеть их дневное изменение. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp", - "propInfo": { - "int": "Интервал логирования в мнутах, частота проверки смены суток в минутах. Не рекомендуется менять", - "logid": "ID накопительной величины которую будем логировать", - "points": "Максимальное количество точек", - "telegram": "График будет отправлять в телеграм репорт с расходами каждый день", - "test": "Параметр необходим для разработчиков. Режим тестирования. График будет обновляться не раз в сутки, а кадый заданный в int интервал." - } + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "График дневного расхода", + "type": "Writing", + "subtype": "LogingDaily", + "id": "log", + "widget": "chart3", + "page": "Графики", + "descr": "Температура", + "num": 1, + "int": 1, + "logid": "t", + "points": 365, + "telegram": 0, + "test": 0, + "btn-defvalue": 0, + "btn-reset": "nil" + } + ], + "about": { + "authorName": "Dmitry Borisenko", + "authorContact": "https://t.me/Dmitry_Borisenko", + "authorGit": "https://github.com/DmitryBorisenko33", + "specialThanks": "@itsid1 @Valiuhaaa Serg", + "moduleName": "LogingDaily", + "moduleVersion": "3.1", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 }, - "defActive": true, - "usedLibs": { - "esp32*": [], - "esp82*": [] + "title": "График дневного расхода", + "moduleDesc": "Расширение позволяющее логировать накопительные величины и видеть их дневное изменение. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp", + "propInfo": { + "int": "Интервал логирования в мнутах, частота проверки смены суток в минутах. Не рекомендуется менять", + "logid": "ID накопительной величины которую будем логировать", + "points": "Максимальное количество точек", + "telegram": "График будет отправлять в телеграм репорт с расходами каждый день", + "test": "Параметр необходим для разработчиков. Режим тестирования. График будет обновляться не раз в сутки, а кадый заданный в int интервал." } + }, + "defActive": true, + "usedLibs": { + "esp32*": [], + "esp82*": [], + "bk72*": [] + } } \ No newline at end of file diff --git a/src/modules/virtual/LogingHourly/LogingHourly.cpp b/src/modules/virtual/LogingHourly/LogingHourly.cpp new file mode 100644 index 00000000..74ff9c74 --- /dev/null +++ b/src/modules/virtual/LogingHourly/LogingHourly.cpp @@ -0,0 +1,347 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPConfiguration.h" +#include "NTP.h" + +class LogingHourly : public IoTItem +{ +private: + String logid; + String id; + String filesList = ""; + + String descr; + + int _publishType = -2; + int _wsNum = -1; + + int points; + + int testMode; + + int telegram; + + IoTItem *dateIoTItem; + + // String prevDate = ""; + String prevHourly = ""; + bool firstTimeInit = true; + + // long interval; + +public: + LogingHourly(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, F("logid"), logid); + jsonRead(parameters, F("id"), id); + jsonRead(parameters, F("points"), points); + jsonRead(parameters, F("test"), testMode); + jsonRead(parameters, F("telegram"), telegram); + jsonRead(parameters, F("descr"), descr); + + long interval; + + jsonRead(parameters, F("int"), interval); + interval = interval * 1000 * 60; // приводим к милисекундам + } + + void doByInterval() + { + if (hasHourlyChanged() || testMode == 1) + { + execute(); + } + } + + void execute() + { + // если объект логгирования не был создан + if (!isItemExist(logid)) + { + SerialPrint("E", F("LogingHourly"), "'" + id + "' LogingHourly object not exist, return"); + return; + } + + String value = getItemValue(logid); + + // если значение логгирования пустое + if (value == "") + { + SerialPrint("E", F("LogingHourly"), "'" + id + "' LogingHourly value is empty, return"); + return; + } + + // если время не было получено из интернета + if (!isTimeSynch) + { + SerialPrint("E", F("LogingHourly"), "'" + id + "' Cant LogingHourly - time not synchronized, return"); + return; + } + + String logData; + + float currentValue = value.toFloat(); + // прочитаем предудущее значение + float prevValue = readDataDB(id + "-v").toFloat(); + // сохраним в базу данных текущее значение, понадобится в следующие час + saveDataDB(id + "-v", value); + + float difference = currentValue - prevValue; + + if (telegram == 1) + { + String msg = descr + ": total " + String(currentValue) + ", consumed " + String(difference); + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->getSubtype() == "TelegramLT" || "Telegram") + { + (*it)->sendTelegramMsg(false, msg); + } + } + } + + // jsonWriteInt(logData, "x", unixTime - 120); + jsonWriteInt(logData, "x", unixTime - 120); + jsonWriteFloat(logData, "y1", difference); + + // прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + + // если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") + { + SerialPrint("E", F("LogingHourly"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData); + return; + } + + // считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("LogingHourly"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + // если количество строк до заданной величины и час и дата не менялась + // if (lines <= points && !hasHourlyChanged()) { + if (lines <= points) + { + // просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData); + } + else + { + String file = readFile(filePath, 2000); + file = deleteBeforeDelimiter(file, "},"); + writeFile(filePath, file); + addNewDataToExistingFile(filePath, logData); + } + } + + void createNewFileWithData(String &logData) + { + logData = logData + ","; + + String path = "/lgh/" + id + "/" + id + ".txt"; // создадим путь вида /lgd/id/id.txt + // создадим пустой файл + if (writeEmptyFile(path) != "success") + { + SerialPrint("E", F("LogingHourly"), "'" + id + "' file writing error, return"); + return; + } + + // запишем в него данные + if (addFile(path, logData) != "success") + { + SerialPrint("E", F("LogingHourly"), "'" + id + "' data writing error, return"); + return; + } + // запишем путь к нему в базу данных + if (saveDataDB(id, path) != "success") + { + SerialPrint("E", F("LogingHourly"), "'" + id + "' db file writing error, return"); + return; + } + #ifdef LIBRETINY + SerialPrint("i", F("LogingHourly"), "'" + id + "' file created http://" + ipToString(WiFi.localIP()) + path); + #else + SerialPrint("i", F("LogingHourly"), "'" + id + "' file created http://" + WiFi.localIP().toString() + path); + #endif + } + + void addNewDataToExistingFile(String &path, String &logData) + { + logData = logData + ","; + if (addFile(path, logData) != "success") + { + SerialPrint("i", F("LogingHourly"), "'" + id + "' file writing error, return"); + return; + }; + #ifdef LIBRETINY + SerialPrint("i", F("LogingHourly"), "'" + id + "' LogingHourly in file http://" + ipToString(WiFi.localIP()) + path); + #else + SerialPrint("i", F("LogingHourly"), "'" + id + "' LogingHourly in file http://" + WiFi.localIP().toString() + path); + #endif + } + const String getTimeLocal_hh() + { + char buf[32]; + sprintf(buf, "%02d", _time_local.hour); + return String(buf); + } + + bool hasHourlyChanged() + { + bool changed = false; + String currentHourly = getTimeLocal_hh(); + if (!firstTimeInit) + { + if (prevHourly != currentHourly) + { + changed = true; + SerialPrint("i", F("NTP"), F("Change hourly event")); +#if defined(ESP8266) + FileFS.gc(); +#endif +#if defined(ESP32) +#endif + } + } + if (isTimeSynch) + firstTimeInit = false; + prevHourly = currentHourly; + return changed; + } + + bool hasDayChanged() + { + bool changed = false; + String currentDate = getTodayDateDotFormated(); + if (!firstTimeInit) + { + if (prevDate != currentDate) + { + changed = true; + SerialPrint("i", F("NTP"), F("Change day event")); +#if defined(ESP8266) + FileFS.gc(); +#endif +#if defined(ESP32) +#endif + } + } + if (isTimeSynch) + firstTimeInit = false; + prevDate = currentDate; + return changed; + } + + void publishValue() + { + String dir = "/lgh/" + id; + filesList = getFilesList(dir); + + SerialPrint("i", F("LogingHourly"), "file list: " + filesList); + + int f = 0; + + while (filesList.length()) + { + String path = selectToMarker(filesList, ";"); + + path = "/lgh/" + id + path; + + f++; + String json = getAdditionalJson(); + if (_publishType == TO_MQTT) + { + publishChartFileToMqtt(path, id, calculateMaxCount()); + } + else if (_publishType == TO_WS) + { + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + } + else if (_publishType == TO_MQTT_WS) + { + publishChartFileToMqtt(path, id, calculateMaxCount()); + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); + } + SerialPrint("i", F("LogingHourly"), String(f) + ") " + path + ", sent"); + + filesList = deleteBeforeDelimiter(filesList, ";"); + } + } + + String getAdditionalJson() + { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; + return json; + } + + void clearHistory() + { + String dir = "/lgh/" + id; + cleanDirectory(dir); + } + + // void publishChartToWsSinglePoint(String value) { + // String topic = mqttRootDevice + "/" + id; + // String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\",\"status\":[{\"x\":" + String(unixTime) + ",\"y1\":" + value + "}]}"; + // String pk = "/string/chart.json|" + json; + // standWebSocket.broadcastTXT(pk); + // } + + void setPublishDestination(int publishType, int wsNum = -1) + { + _publishType = publishType; + _wsNum = wsNum; + } + + String getValue() + { + return ""; + } + /* + void loop() { + if (enableDoByInt) { + currentMillis = millis(); + difference = currentMillis - prevMillis; + if (difference >= interval) { + prevMillis = millis(); + this->doByInterval(); + } + } + } + */ + // просто максимальное количество точек + int calculateMaxCount() + { + // return 1440;//1440 + return 3600; // 1440 + } + + void onModuleOrder(String &key, String &value) + { + if (key == "defvalue") + { + saveDataDB(id + "-v", value); + SerialPrint("i", F("LogingHourly"), "User set default value: " + value); + } + else if (key == "reset") + { + clearHistory(); + SerialPrint("i", F("LogingHourly"), F("User clean chart history")); + } + } +}; + +void *getAPI_LogingHourly(String subtype, String param) +{ + if (subtype == F("LogingHourly")) + { + return new LogingHourly(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/virtual/LogingHourly/modinfo.json b/src/modules/virtual/LogingHourly/modinfo.json new file mode 100644 index 00000000..bcb18142 --- /dev/null +++ b/src/modules/virtual/LogingHourly/modinfo.json @@ -0,0 +1,50 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "График часового расхода", + "type": "Writing", + "subtype": "LogingHourly", + "id": "logh", + "widget": "chart3", + "page": "Графики", + "descr": "Расход в час", + "num": 1, + "int": 1, + "logid": "", + "points": 24, + "telegram": 0, + "test": 0, + "btn-defvalue": 0, + "btn-reset": "nil" + } + ], + "about": { + "authorName": "AVAKS", + "authorContact": "https://t.me/avaks", + "authorGit": "https://github.com/avaksru", + "specialThanks": "@itsid1 @Valiuhaaa Serg @Serghei63", + "moduleName": "LogingHourly", + "moduleVersion": "2", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "График часового расхода", + "moduleDesc": "Расширение позволяющее логировать накопительные величины и видеть их часовое изменение. Графики доступны в мобильном приложении и в веб интерфейсе. Данные графиков хранятся в встроенной памяти esp", + "propInfo": { + "int": "Интервал логирования в мнутах, частота проверки смены часа в минутах. Не рекомендуется менять", + "logid": "ID накопительной величины которую будем логировать", + "points": "Максимальное количество точек", + "telegram": "График будет отправлять в телеграм репорт с расходами каждый час", + "test": "Параметр необходим для разработчиков. Режим тестирования. График будет обновляться не раз в час, а кадый заданный в int интервал." + } + }, + "defActive": true, + "usedLibs": { + "esp32*": [], + "esp82*": [], + "bk72*": [] + } +} \ No newline at end of file diff --git a/src/modules/virtual/Math/modinfo.json b/src/modules/virtual/Math/modinfo.json index 6ae5c83a..e0cf20a9 100644 --- a/src/modules/virtual/Math/modinfo.json +++ b/src/modules/virtual/Math/modinfo.json @@ -56,6 +56,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } diff --git a/src/modules/virtual/Timer/modinfo.json b/src/modules/virtual/Timer/modinfo.json index 7e3ee204..941235a2 100644 --- a/src/modules/virtual/Timer/modinfo.json +++ b/src/modules/virtual/Timer/modinfo.json @@ -74,6 +74,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/virtual/UpdateServer/UpdateServer.cpp b/src/modules/virtual/UpdateServer/UpdateServer.cpp new file mode 100644 index 00000000..b9f6b75a --- /dev/null +++ b/src/modules/virtual/UpdateServer/UpdateServer.cpp @@ -0,0 +1,27 @@ +#include "Global.h" +#include "classes/IoTItem.h" + +class UpdateServer : public IoTItem { + public: + UpdateServer(String parameters) : IoTItem(parameters) {} + + void onModuleOrder(String &key, String &value) { + if (key == "startUpdateAll") { + upgrade_firmware(3, value); + } else if (key == "startUpdateFS") { + upgrade_firmware(2, value); + } else if (key == "startUpdateFW") { + upgrade_firmware(1, value); + } + } + + ~UpdateServer() {}; +}; + +void* getAPI_UpdateServer(String subtype, String param) { + if (subtype == F("UpdateServer")) { + return new UpdateServer(param); + } else { + return nullptr; + } +} diff --git a/src/modules/virtual/UpdateServer/modinfo.json b/src/modules/virtual/UpdateServer/modinfo.json new file mode 100644 index 00000000..c4b8fc70 --- /dev/null +++ b/src/modules/virtual/UpdateServer/modinfo.json @@ -0,0 +1,41 @@ +{ + "menuSection": "virtual_elments", + "configItem": [ + { + "global": 0, + "name": "Свой сервер обновлений", + "type": "Reading", + "subtype": "UpdateServer", + "id": "UpdateServer", + "widget": "", + "page": "", + "descr": "", + "btn-startUpdateAll": "http://192.168.11.112/iotm/esp8266_4mb/400", + "btn-startUpdateFS": "http://192.168.11.112/iotm/esp8266_4mb/400", + "btn-startUpdateFW": "http://192.168.11.112/iotm/esp8266_4mb/400" + } + ], + "about": { + "authorName": "Ilya Belyakov", + "authorContact": "https://t.me/Biveraxe", + "authorGit": "https://github.com/biveraxe", + "exampleURL": "https://iotmanager.org/", + "specialThanks": "", + "moduleName": "UpdateServer", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Свой сервер обновлений", + "moduleDesc": "Модуль для получения прошивки из своего сервера обновлений.", + "propInfo": { + "btn-startUpdate": "Кнопка запуска процесса обновления из указанного URL" + } + }, + "defActive": true, + "usedLibs": { + "esp32*": [], + "esp82*": [] + } +} \ No newline at end of file diff --git a/src/modules/virtual/VButton/modinfo.json b/src/modules/virtual/VButton/modinfo.json index 8a61b2d1..68824a83 100644 --- a/src/modules/virtual/VButton/modinfo.json +++ b/src/modules/virtual/VButton/modinfo.json @@ -36,6 +36,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/virtual/Variable/modinfo.json b/src/modules/virtual/Variable/modinfo.json index bf91bb7a..77ed8a83 100644 --- a/src/modules/virtual/Variable/modinfo.json +++ b/src/modules/virtual/Variable/modinfo.json @@ -96,6 +96,7 @@ "defActive": true, "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } \ No newline at end of file diff --git a/src/modules/virtual/owmWeather/modinfo.json b/src/modules/virtual/owmWeather/modinfo.json index 5940d5db..46502d0d 100644 --- a/src/modules/virtual/owmWeather/modinfo.json +++ b/src/modules/virtual/owmWeather/modinfo.json @@ -88,6 +88,7 @@ "usedLibs": { "esp32*": [], - "esp82*": [] + "esp82*": [], + "bk72*": [] } } diff --git a/src/modules/virtual/owmWeather/owmWeather.cpp b/src/modules/virtual/owmWeather/owmWeather.cpp index 1ba7b0e9..54abd407 100644 --- a/src/modules/virtual/owmWeather/owmWeather.cpp +++ b/src/modules/virtual/owmWeather/owmWeather.cpp @@ -77,7 +77,7 @@ class owmWeather : public IoTItem if (httpCode > 0) { - ret = httpCode; + ret = String(httpCode); if (httpCode == HTTP_CODE_OK) { diff --git a/src/utils/FileUtils.cpp b/src/utils/FileUtils.cpp index c12bffbe..b6f5ee30 100644 --- a/src/utils/FileUtils.cpp +++ b/src/utils/FileUtils.cpp @@ -60,9 +60,12 @@ File seekFile(const String& filename, size_t position) { const String writeFile(const String& filename, const String& str) { String path = filepath(filename); -#ifdef ESP32 +#if defined ESP32 auto file = FileFS.open(path, FILE_WRITE, true); #endif +#if defined LIBRETINY + auto file = FileFS.open(path, FILE_WRITE); +#endif #ifdef ESP8266 auto file = FileFS.open(path, FILE_WRITE); #endif @@ -77,9 +80,12 @@ const String writeFile(const String& filename, const String& str) { const String writeEmptyFile(const String& filename) { String path = filepath(filename); -#ifdef ESP32 +#if defined ESP32 auto file = FileFS.open(path, FILE_WRITE, true); #endif +#if defined LIBRETINY + auto file = FileFS.open(path, FILE_WRITE); +#endif #ifdef ESP8266 auto file = FileFS.open(path, FILE_WRITE); #endif @@ -285,7 +291,7 @@ String getFilesList(String& directory) { #endif } -#if defined(ESP8266) +#if defined(ESP8266) || defined (LIBRETINY) bool getInfo(FSInfo& info) { return FileFS.info(info); } // Информация о ФС diff --git a/src/utils/I2CUtils.cpp b/src/utils/I2CUtils.cpp index a8c643c6..3d26fff4 100644 --- a/src/utils/I2CUtils.cpp +++ b/src/utils/I2CUtils.cpp @@ -1,3 +1,4 @@ +#ifndef LIBRETINY #include #include "utils/SerialPrint.h" @@ -13,14 +14,14 @@ void scanI2C() { if (error == 0){ message += "I2C device found at address 0x"; - message += uint64ToString(address, 16); + message += uint64ToStringIoTM(address, 16); message += " !"; nDevices++; } else if (error==4) { message += "Unknow error at address 0x"; - message += uint64ToString(address, 16); + message += uint64ToStringIoTM(address, 16); } } if (nDevices == 0) @@ -29,4 +30,5 @@ void scanI2C() { message += "done\n"; SerialPrint("i", "I2C Scaner", message); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/utils/Statistic.cpp b/src/utils/Statistic.cpp index 1a992514..b9f359d8 100644 --- a/src/utils/Statistic.cpp +++ b/src/utils/Statistic.cpp @@ -36,7 +36,11 @@ void updateDeviceStatus() { if (httpResponseCode > 0) { ret = http.errorToString(httpResponseCode).c_str(); if (httpResponseCode == HTTP_CODE_OK) { + #ifndef LIBRETINY String payload = http.getString(); + #else + String payload = httpGetString(http); + #endif ret += " " + payload; } } else { diff --git a/src/utils/StringUtils.cpp b/src/utils/StringUtils.cpp index f33db48c..80cfa61a 100644 --- a/src/utils/StringUtils.cpp +++ b/src/utils/StringUtils.cpp @@ -103,6 +103,7 @@ uint8_t hexStringToUint8(const String& hex) { if (tmp >= 0x00 && tmp <= 0xFF) { return tmp; } + return 0; } uint16_t hexStringToUint16(const String& hex) { @@ -110,6 +111,15 @@ uint16_t hexStringToUint16(const String& hex) { if (tmp >= 0x0000 && tmp <= 0xFFFF) { return tmp; } + return 0; +} + +uint32_t hexStringToUint32(const String& hex) { + uint32_t tmp = strtol(hex.c_str(), NULL, 0); + if (tmp >= 0x0000 && tmp <= 0xFFFFFF) { + return tmp; + } + return 0; } size_t itemsCount2(String str, const String& separator) { @@ -183,7 +193,7 @@ String prettyBytes(size_t size) { return String(size / 1024.0 / 1024.0 / 1024.0) + "GB"; } -String uint64ToString(uint64_t input, uint8_t base) { +String uint64ToStringIoTM(uint64_t input, uint8_t base) { String result = ""; do { @@ -223,4 +233,29 @@ bool strInVector(const String& str, const std::vector& vec) { if (vec[i] == str) return true; } return false; +} + +String getUtf8CharByIndex(const String& utf8str, int index) { + if (index < 0) index = 0; + + int len = utf8str.length(); + int charCount = 0; + int i = 0; + while (i < len) { + int charLen = 1; + unsigned char c = utf8str[i]; + if ((c & 0x80) == 0x00) charLen = 1; // 0xxxxxxx + else if ((c & 0xE0) == 0xC0) charLen = 2; // 110xxxxx + else if ((c & 0xF0) == 0xE0) charLen = 3; // 1110xxxx + else if ((c & 0xF8) == 0xF0) charLen = 4; // 11110xxx + + if (charCount == index) { + return utf8str.substring(i, i + charLen); + } + + if (i + charLen >= len) return utf8str.substring(i, i + charLen); + i += charLen; + charCount++; + } + return ""; } \ No newline at end of file diff --git a/src/utils/WiFiUtils.cpp b/src/utils/WiFiUtils.cpp index b2e0fb2f..0bda37e0 100644 --- a/src/utils/WiFiUtils.cpp +++ b/src/utils/WiFiUtils.cpp @@ -1,14 +1,403 @@ #include "utils/WiFiUtils.h" #include -#define TRIESONE 25 // количество попыток подключения к одной сети из несколких -#define TRIES 40 // количество попыток подключения сети если она одна +#if defined(ESP32) +#include +#endif +#include "DebugTrace.h" +#define TRIESONE 20 // количество секунд ожидания подключения к одной сети из несколких +#define TRIES 30 // количество секунд ожидания подключения сети если она одна -void routerConnect() +#if defined(esp32_wifirep) +#include "lwip/lwip_napt.h" +// #include "lwip/ip_route.h" +#define PROTO_TCP 6 +#define PROTO_UDP 17 + +IPAddress stringToIp(String strIp) +{ + IPAddress ip; + ip.fromString(strIp); + return ip; +} +#endif +void addPortMap(String TCP_UDP, String maddr, u16_t mport, String daddr, u16_t dport) +{ +#if defined(esp32_wifirep) + uint8_t tcp_udp; + if (TCP_UDP == "TCP") + tcp_udp = PROTO_TCP; + else if (TCP_UDP == "UDP") + tcp_udp = PROTO_UDP; + else + SerialPrint("E", "WIFI", "Add port map: ERROR, Must be 'TCP' or 'UDP'"); + + ip_portmap_add(tcp_udp, stringToIp(maddr), mport, stringToIp(daddr), dport); + SerialPrint("i", "WIFI", "Add port map: " + String(tcp_udp) + ", " + maddr + ":" + String(mport) + " -> " + daddr + ":" + String(dport)); +#else + SerialPrint("E", "WIFI", "Add port map: ERROR, change board to esp32_wifirep"); +#endif +} + + + +#ifdef WIFI_ASYNC +std::vector _ssidList; +std::vector _passwordList; +// номер сети, для перебирания в момент подключения к сетям из массива +volatile uint8_t currentNetwork = 0; +volatile bool wifiConnecting = false; +volatile uint8_t connectionAttempts = 0; +//------------------------------------------ +// Обработчики событий Wi-Fi +//------------------------------------------ +void WiFiEvent(arduino_event_t *event) +{ + switch (event->event_id) + { +#if defined(LIBRETINY) || defined(esp32c6_4mb) || defined(esp32c6_8mb) + case ARDUINO_EVENT_WIFI_STA_CONNECTED: +#else + case SYSTEM_EVENT_STA_CONNECTED: +#endif + // Подключились к STA + SerialPrint("i", "WIFI", "Connected to AP: " + WiFi.SSID()); + // TODO если подключились, но не получили IP что будет? + break; +#if defined(LIBRETINY) || defined(esp32c6_4mb) || defined(esp32c6_8mb) + case ARDUINO_EVENT_WIFI_STA_GOT_IP: +#else + case SYSTEM_EVENT_STA_GOT_IP: +#endif + // Получили IP от роутера + // wifiReconnectTicker.detach(); + ts.remove(WIFI_SCAN); + ts.remove(WIFI_CONN); +#ifdef LIBRETINY + SerialPrint("i", "WIFI", "http://" + ipToString(WiFi.localIP())); + jsonWriteStr(settingsFlashJson, "ip", ipToString(WiFi.localIP())); +#else + SerialPrint("i", "WIFI", "http://" + WiFi.localIP().toString()); + jsonWriteStr(settingsFlashJson, "ip", WiFi.localIP().toString()); +#endif + createItemFromNet("onWifi", "1", 1); + // запускаем MQTT + mqttInit(); + SerialPrint("i", F("WIFI"), F("Network Init")); + + bool postMsgTelegram; + if (!jsonRead(settingsFlashJson, "debugTraceMsgTlgrm", postMsgTelegram, false)) postMsgTelegram = 1; + sendDebugTraceAndFreeMemory(postMsgTelegram); + + // Отключаем AP при успешном подключении + WiFi.softAPdisconnect(true); + break; +#if defined(LIBRETINY) || defined(esp32c6_4mb) || defined(esp32c6_8mb) + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: +#else + case SYSTEM_EVENT_STA_DISCONNECTED: +#endif + // Отключились от STA + SerialPrint("i", "WIFI", "Disconnected from STA"); + // Завершаем задачу проверки сети + ts.remove(WIFI_CONN); + if (wifiConnecting) + { // если у нас ещё не закончились попытки подключения, то перезапускаем задачу + Serial.print("."); + checkConnection(); + // wifiReconnectTicker.once_ms(WIFI_CHECK_INTERVAL, checkConnection); + } + else + { // если попытки подключения исчерпаны, то переходим в AP + sendDebugTraceAndFreeMemory(false); + startAPMode(); + } + break; +#if defined(LIBRETINY) || defined(esp32c6_4mb) || defined(esp32c6_8mb) + case ARDUINO_EVENT_WIFI_SCAN_DONE: +#else + case SYSTEM_EVENT_SCAN_DONE: +#endif + if (WiFi.scanComplete() >= 0) { + Serial.println("Valid Scan completed"); + handleScanResults(); + WiFi.scanDelete(); // Очищаем только при успешном сканировании + } else { + //Serial.println("Empty scan or error"); + //WiFi.scanDelete(); // Принудительная очистка буфера + } + // Завершилось сканирование сетей + //Serial.println("Scan completed"); + //handleScanResults(); + break; + } +} + +//------------------------------------------ +// Обработка результатов сканирования +//------------------------------------------ +void handleScanResults() +{ + ssidListHeapJson = "{}"; + _ssidList.clear(); + _passwordList.clear(); + jsonReadArray(settingsFlashJson, "routerssid", _ssidList); + jsonReadArray(settingsFlashJson, "routerpass", _passwordList); + int16_t numNetworks = WiFi.scanComplete(); + if (numNetworks <= 0) + { + SerialPrint("i", "WIFI", "no networks found"); + return; + } + + // Ищем известные сети + int connectNumNet = -1; + // SerialPrint("i", "WIFI", "Count found: "+numNetworks); + for (int n = 0; n < numNetworks; ++n) + { + String ssid = WiFi.SSID(n); + jsonWriteStr_(ssidListHeapJson, String(n), ssid); + for (size_t i = 0; i < _ssidList.size(); i++) + { + if (ssid == _ssidList[i]) + { + Serial.printf("Found known network: %s\n", _ssidList[i]); + if (connectNumNet < 0) + connectNumNet = i; + } + } + // if + } + sendStringToWs("ssidli", ssidListHeapJson, -1); + SerialPrint("i", "WIFI", "Scan Found: " + ssidListHeapJson); + if (connectNumNet >= 0 && !isNetworkActive()) + { + // ts.remove(WIFI_SCAN); + connectToNextNetwork(); + } + // checkConnection(); + // connectToSTA(_ssidList[connectNumNet], _passwordList[connectNumNet]); + WiFi.scanDelete(); +} + +void WiFiUtilsItit() { +#if !defined LIBRETINY +#if defined(esp32c6_4mb) || defined(esp32c6_8mb) + WiFi.setAutoReconnect(false); +#else WiFi.setAutoConnect(false); - WiFi.persistent(false); +#endif + WiFi.persistent(true); // Сохраняет текущую сеть при сканировании + WiFi.setSleep(true); +#endif + WiFi.mode(WIFI_STA); + WiFi.onEvent(WiFiEvent); + _ssidList.clear(); + _passwordList.clear(); + jsonReadArray(settingsFlashJson, "routerssid", _ssidList); + jsonReadArray(settingsFlashJson, "routerpass", _passwordList); + + if (_ssidList.empty() || _passwordList.empty()) + { + SerialPrint("E", "WIFI", "No networks configured"); + startAPMode(); + return; + } + currentNetwork = 0; + connectionAttempts = 0; + connectToNextNetwork(); +} + +void connectToNextNetwork() +{ + // все сети перебрали + if (currentNetwork >= _ssidList.size()) + { + SerialPrint("i", "WIFI", "All networks tried"); + ts.remove(WIFI_CONN); + startAPMode(); + return; + } + + wifiConnecting = true; + // connectionAttempts++; + + const char *ssid = _ssidList[currentNetwork].c_str(); + const char *pass = _passwordList[currentNetwork].c_str(); + // Пробуем подключиться к сети + SerialPrint("i", "WIFI", "Connecting to: " + String(ssid)); + WiFi.begin(ssid, pass); + +#if defined(ESP32) + WiFi.setTxPower(WIFI_POWER_19_5dBm); +#elif defined(ESP8266) + WiFi.setOutputPower(20.5); +#endif + // проверяем статус подключения и перебираем сети если таймаут не вышел + checkConnection(); + + // wifiReconnectTicker.once_ms(WIFI_CHECK_INTERVAL, checkConnection); +} + +void checkConnection() +{ + ts.add( + WIFI_CONN, 1000, + [&](void *) + { + connectionAttempts++; + if (WiFi.status() == WL_CONNECTED) + { + connectionAttempts = 0; + wifiConnecting = false; + return; + } + + if (connectionAttempts >= (_ssidList.size() > 1 ? TRIESONE : TRIES)) + { + SerialPrint("i", "WIFI", "Max attempts reached"); + currentNetwork++; + connectionAttempts = 0; + wifiConnecting = false; + } + + if (wifiConnecting) + { +#ifdef ESP8266 + if (WiFi.status() == WL_CONNECT_FAILED || WiFi.status() == WL_WRONG_PASSWORD) +#else + if (WiFi.status() == WL_CONNECT_FAILED) +#endif + { + SerialPrint("E", "WIFI", "Connection failed, wrong password?"); + jsonWriteInt(errorsHeapJson, "passer", 1); + currentNetwork++; + connectionAttempts = 0; + } + + // wifiReconnectTicker.once_ms(WIFI_CHECK_INTERVAL, checkConnection); + } + + if (!wifiConnecting) + { + connectToNextNetwork(); + } + }, + nullptr, true); +} + +//------------------------------------------ +// Неблокирующее подключение к STA +//------------------------------------------ +void connectToSTA(const char *ssid, const char *pass) +{ + if (isNetworkActive()) + return; + SerialPrint("i", "WIFI", "Connecting to ... " + String(ssid)); + // SerialPrint("i", "WIFI", "pass connect: " + _passwordList[i]); + WiFi.begin(ssid, pass); +#if defined(ESP32) + WiFi.setTxPower(WIFI_POWER_19_5dBm); +#elif defined(ESP8266) + WiFi.setOutputPower(20.5); +#endif +} +void ScanAsync() +{ + // bool res = false; + int n = WiFi.scanComplete(); + SerialPrint("i", "WIFI", "scan result: " + String(n, DEC)); + + if (n == -1) + { // Сканирование все еще выполняется + SerialPrint("i", "WIFI", "scanning in progress"); + } + else + { + SerialPrint("i", "WIFI", "start scanning"); + WiFi.scanNetworks(true, false); + } +} +#else //WIFI_ASYNC + +void routerConnect() +{ +#if defined(esp32_wifirep) +// Set custom dns server address for dhcp server +#define MY_DNS_IP_ADDR 0xC0A80401 // 192.168.4.1 // 0x08080808 // 8.8.8.8 + ip_addr_t dnsserver; + + String _ssidAP = jsonReadStr(settingsFlashJson, "apssid"); + String _passwordAP = jsonReadStr(settingsFlashJson, "appass"); + int _chanelAP = 0; + jsonRead(settingsFlashJson, "wifirep_apchanel", _chanelAP); + if (_chanelAP == 0) + _chanelAP = 7; + + // WiFi.begin(ssid, password); + WiFi.mode(WIFI_AP_STA); + + String s_apip = ""; + bool ap_ip = jsonRead(settingsFlashJson, "wifirep_apip", s_apip); + if (ap_ip && s_apip != "") + { + WiFi.softAPConfig(stringToIp(s_apip), stringToIp(s_apip), stringToIp("255.255.255.0")); + // bool softAPConfig(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dhcp_lease_start = (uint32_t) 0); + dnsserver.u_addr.ip4.addr = stringToIp(s_apip); + } + else + dnsserver.u_addr.ip4.addr = htonl(MY_DNS_IP_ADDR); + + dnsserver.type = IPADDR_TYPE_V4; + dhcps_dns_setserver(&dnsserver); + + WiFi.softAP(_ssidAP.c_str(), _passwordAP.c_str(), _chanelAP, 0, 5); + jsonWriteStr(settingsFlashJson, "ip", WiFi.softAPIP().toString()); + SerialPrint("i", "WIFI", "AP SSID: " + WiFi.softAPSSID()); + SerialPrint("i", "WIFI", "AP IP: " + WiFi.softAPIP().toString()); + SerialPrint("i", "WIFI", "AP pass: " + _passwordAP); + + String s_staip = ""; + bool static_ip = jsonRead(settingsFlashJson, "wifirep_staip", s_staip); + String s_gateway = jsonReadStr(settingsFlashJson, "wifirep_gateway"); + String s_netmask = jsonReadStr(settingsFlashJson, "wifirep_netmask"); + String s_dns = jsonReadStr(settingsFlashJson, "wifirep_dns"); + + if (static_ip == true && s_staip != "") + { + SerialPrint("i", "WIFI", "Use static IP"); + WiFi.config(stringToIp(s_staip), stringToIp(s_gateway), stringToIp(s_netmask), stringToIp(s_dns)); + // bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = (uint32_t)0x00000000, IPAddress dns2 = (uint32_t)0x00000000); + SerialPrint("i", "WIFI", "Static IP: " + s_staip); + SerialPrint("i", "WIFI", "Gateway: " + s_gateway); + SerialPrint("i", "WIFI", "Netmask: " + s_netmask); + SerialPrint("i", "WIFI", "DNS: " + s_dns); + } +#else WiFi.mode(WIFI_STA); +#endif + +#if !defined LIBRETINY +#if defined(esp32c6_4mb) || defined(esp32c6_8mb) + WiFi.setAutoReconnect(false); +#else + WiFi.setAutoConnect(false); +#endif + WiFi.persistent(false); +#endif +/* String s_staip = "192.168.2.62"; + String s_gateway = "192.168.2.1"; + String s_netmask = "255.255.255.0"; + String s_dns = "192.168.2.1"; + SerialPrint("i", "WIFI", "Use static IP"); + WiFi.config(stringToIp(s_staip), stringToIp(s_gateway), stringToIp(s_netmask), stringToIp(s_dns)); + // bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = (uint32_t)0x00000000, IPAddress dns2 = (uint32_t)0x00000000); + SerialPrint("i", "WIFI", "Static IP: " + s_staip); + SerialPrint("i", "WIFI", "Gateway: " + s_gateway); + SerialPrint("i", "WIFI", "Netmask: " + s_netmask); + SerialPrint("i", "WIFI", "DNS: " + s_dns); */ + //WiFi.mode(WIFI_STA); byte triesOne = TRIESONE; std::vector _ssidList; @@ -20,14 +409,16 @@ void routerConnect() if (_passwordList.size() == 0 && _ssidList[0] == "" && _passwordList[0] == "") { + #ifndef LIBRETINY WiFi.begin(); + #endif } else { WiFi.begin(_ssidList[0].c_str(), _passwordList[0].c_str()); -#ifdef ESP32 +#if defined (ESP32) WiFi.setTxPower(WIFI_POWER_19_5dBm); -#else +#elif defined (ESP8266) WiFi.setOutputPower(20.5); #endif String _ssid; @@ -65,6 +456,12 @@ void routerConnect() jsonWriteInt(errorsHeapJson, "passer", 1); break; } +#if defined(ESP32) + //SerialPrint("i", "Task", "Resetting WDT..."); + #if !defined(esp32c6_4mb) && !defined(esp32c6_8mb) //TODO esp32-c6 переписать esp_task_wdt_init + esp_task_wdt_reset(); + #endif +#endif Serial.print("."); delay(1000); } @@ -79,16 +476,45 @@ void routerConnect() else { Serial.println(""); +#ifdef LIBRETINY + SerialPrint("i", "WIFI", "http://" + ipToString(WiFi.localIP())); + jsonWriteStr(settingsFlashJson, "ip", ipToString(WiFi.localIP())); +#else SerialPrint("i", "WIFI", "http://" + WiFi.localIP().toString()); jsonWriteStr(settingsFlashJson, "ip", WiFi.localIP().toString()); +#endif + createItemFromNet("onWifi", "1", 1); + +#if defined(esp32_wifirep) + // Enable DNS (offer) for dhcp server + dhcps_offer_t dhcps_dns_value = OFFER_DNS; + dhcps_set_option_info(6, &dhcps_dns_value, sizeof(dhcps_dns_value)); + u32_t napt_netif_ip; + if (ap_ip && s_apip != "") + napt_netif_ip = stringToIp(s_apip); + else + { + napt_netif_ip = 0xC0A80401; // Set to ip address of softAP netif (Default is 192.168.4.1) + napt_netif_ip = htonl(napt_netif_ip); + } + // get_esp_interface_netif(ESP_IF_WIFI_AP) + ip_napt_enable(napt_netif_ip, 1); + // ip_napt_enable_no(ESP_IF_WIFI_AP, 1); + +#endif mqttInit(); } SerialPrint("i", F("WIFI"), F("Network Init")); } - +#endif bool startAPMode() { +#ifdef WIFI_ASYNC + wifiConnecting = false; + currentNetwork = 0; + connectionAttempts = 0; +#endif SerialPrint("i", "WIFI", "AP Mode"); WiFi.disconnect(); @@ -96,19 +522,25 @@ bool startAPMode() String _ssidAP = jsonReadStr(settingsFlashJson, "apssid"); String _passwordAP = jsonReadStr(settingsFlashJson, "appass"); - - WiFi.softAP(_ssidAP.c_str(), _passwordAP.c_str()); + if (_passwordAP == "") + WiFi.softAP(_ssidAP.c_str(), NULL, 6); + else + WiFi.softAP(_ssidAP.c_str(), _passwordAP.c_str(), 6); IPAddress myIP = WiFi.softAPIP(); - +#ifdef LIBRETINY + SerialPrint("i", "WIFI", "AP IP: " + ipToString(myIP)); + jsonWriteStr(settingsFlashJson, "ip", ipToString(myIP)); +#else SerialPrint("i", "WIFI", "AP IP: " + myIP.toString()); jsonWriteStr(settingsFlashJson, "ip", myIP.toString()); - +#endif if (jsonReadInt(errorsHeapJson, "passer") != 1) { ts.add( WIFI_SCAN, 30 * 1000, [&](void *) { +#ifndef WIFI_ASYNC std::vector jArray; jsonReadArray(settingsFlashJson, "routerssid", jArray); for (int8_t i = 0; i < jArray.size(); i++) @@ -121,12 +553,16 @@ bool startAPMode() WiFi.scanDelete(); routerConnect(); } +#else + ScanAsync(); +#endif }, nullptr, true); } return true; } +#ifndef WIFI_ASYNC boolean RouterFind(std::vector jArray) { bool res = false; @@ -170,32 +606,87 @@ boolean RouterFind(std::vector jArray) WiFi.scanDelete(); return res; } +#endif -boolean isNetworkActive() { - return WiFi.status() == WL_CONNECTED; +boolean isNetworkActive() +{ + return WiFi.status() == WL_CONNECTED; } -uint8_t getNumAPClients() { - return WiFi.softAPgetStationNum(); +uint8_t getNumAPClients() +{ + return WiFi.softAPgetStationNum(); } -uint8_t RSSIquality() { - uint8_t res = 0; - if (isNetworkActive()) { - int rssi = WiFi.RSSI(); - if (rssi >= -50) { - res = 6; //"Excellent"; - } else if (rssi < -50 && rssi >= -60) { - res = 5; //"Very good"; - } else if (rssi < -60 && rssi >= -70) { - res = 4; //"Good"; - } else if (rssi < -70 && rssi >= -80) { - res = 3; //"Low"; - } else if (rssi < -80 && rssi > -100) { - res = 2; //"Very low"; - } else if (rssi <= -100) { - res = 1; //"No signal"; - } +uint8_t RSSIquality() +{ + uint8_t res = 0; + if (isNetworkActive()) + { + int rssi = WiFi.RSSI(); + if (rssi >= -50) + { + res = 6; //"Excellent"; + } + else if (rssi < -50 && rssi >= -60) + { + res = 5; //"Very good"; } - return res; + else if (rssi < -60 && rssi >= -70) + { + res = 4; //"Good"; + } + else if (rssi < -70 && rssi >= -80) + { + res = 3; //"Low"; + } + else if (rssi < -80 && rssi > -100) + { + res = 2; //"Very low"; + } + else if (rssi <= -100) + { + res = 1; //"No signal"; + } + } + return res; +} + +#ifdef LIBRETINY +String httpGetString(HTTPClient &http) +{ + String payload = ""; + int len = http.getSize(); + uint8_t buff[128] = {0}; + WiFiClient *stream = http.getStreamPtr(); + + // read all data from server + while (http.connected() && (len > 0 || len == -1)) + { + // get available data size + size_t size = stream->available(); + + if (size) + { + // read up to 128 byte + int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + + // write it to Serial + // Serial.write(buff,c); + + // payload += String((char*)buff); + char charBuff[c + 1]; // Create a character array with space for null terminator + memcpy(charBuff, buff, c); // Copy the data to the character array + charBuff[c] = '\0'; // Null-terminate the character array + payload += String(charBuff); // Append the character array to the payload + + if (len > 0) + { + len -= c; + } + } + delay(1); + } + return payload; } +#endif \ No newline at end of file diff --git a/tools/lt_fsbuild.py b/tools/lt_fsbuild.py new file mode 100644 index 00000000..4eb04226 --- /dev/null +++ b/tools/lt_fsbuild.py @@ -0,0 +1,38 @@ + +# c:\Users\bmw\.platformio\penv\Scripts\pio run -t buildfs +#c:\Users\bmw\.platformio\penv\.libretiny\Scripts\ltchiptool -v flash write lt_littlefs.bin --start 0x1db000 -f BK7231N + + + +Import("env") + +# Определите имя таргета +target_name = "buildfs" + +# Получите путь к директории с данными из переменной среды env +data_dir = env.subst("$PROJECT_DIR") + "/data_svelte" + +# Получите путь к выходному файлу LittleFS из переменной среды env +output_file = env.subst("$PROJECT_DIR") + "/lt_littlefs.bin" + +# Получите путь к mklittlefs.exe из переменной среды env +mklittlefs_path = env.subst("$PROJECT_DIR") + "/tools/mklittlefs.exe" + +# Определите команду, которая будет выполнена при вызове таргета +command = "{} -c {} -s 0x25000 -p 0x100 -b 0x1000 {}".format(mklittlefs_path, data_dir, output_file) + +# Добавьте кастомный таргет +env.AddCustomTarget( + name=target_name, + dependencies=None, + actions=[command], + title="Build LittleFS", + description="Build LittleFS file system" +) + + +# Определите путь к исполняемому файлу ltchiptool +ltchiptool_path = "${LTCHIPTOOL}" + + +print("Custom target '{}' added!".format(target_name)) \ No newline at end of file diff --git a/tools/lt_fsbuildscript.py b/tools/lt_fsbuildscript.py new file mode 100644 index 00000000..b636aa45 --- /dev/null +++ b/tools/lt_fsbuildscript.py @@ -0,0 +1,58 @@ + +#import os +import subprocess +#import json +#from datetime import datetime +from os.path import basename, join, normpath +Import("env") +from platformio.platform.base import PlatformBase +from platformio.platform.board import PlatformBoardConfig +#from SCons.Script import DefaultEnvironment, Environment +#env: Environment = DefaultEnvironment() +platform: PlatformBase = env.PioPlatform() + +def build_ltfs(): + print("Current Data dir", env.get("PROJECT_DATA_DIR")) + pathmk = env.get("PROJECT_DIR")+ "/tools/mklittlefs.exe" + pathmk = normpath(pathmk).replace("\\", "/") + print("Current tools dir", pathmk) + FS_SIZE ="0x25000" + FS_PAGE = "0x100" + FS_BLOCK = "0x1000" + subprocess.call([pathmk, "-c", env.get("PROJECT_DATA_DIR"), "-s", FS_SIZE, "-p", FS_PAGE, "-b", FS_BLOCK, "lt_littlefs.bin", ]) + print("------------!!!!!!!!!-------------") + print("FS_SIZE", FS_SIZE) + print("FS_PAGE", FS_PAGE) + print("FS_BLOCK", FS_BLOCK) + print("------------!!!!!!!!!-------------") + #subprocess.call(["c:/USER_BMW/IoTManager_4dev/IoTManager_C6_Tiny/tools/mklittlefs.exe", '-c', '${SOURCES}', '-s', '${FS_SIZE}', '-p', '${FS_PAGE}', '-b', '${FS_BLOCK}', 'lt_littlefs.bin']) + # ./mklittlefs.exe -c c:\USER_BMW\IoTManager_4dev\IoTManager_C6_Tiny\data_svelte -s 0x25000 -p 0x100 -b 0x1000 lt_littlefs.bin +def upload_ltfs(): + print("Flash UserData offset", env.get("FLASH_USERDATA_OFFSET")) + print("Flash UserData length", env.get("FLASH_USERDATA_LENGTH")) + LT_TOOL = 'python.exe -m ltchiptool -r -i 1 -L ' + platform.get_dir() + 'flash write' + print(LT_TOOL) + #subprocess.call(LT_TOOL , shell=True) + #"${LTCHIPTOOL}" +def before_build(): # source, target, env + print("Current Build targets", BUILD_TARGETS) + # Это всё потому что не работает "buildprog". При сборке прошивки Targets пустой, на всякий случай исключим все остальные + if (BUILD_TARGETS == ['upload'] or + BUILD_TARGETS == ['buildfs'] or + BUILD_TARGETS == ['uploadfs'] or + BUILD_TARGETS == ['uploadfsota'] or + BUILD_TARGETS == ['size']): + return + + #print("Clear BUILD_TIME, delete main.o !") + # config = configparser.ConfigParser() # создаём объекта парсера INI + # config.read("platformio.ini") + #deviceName = config["platformio"]["default_envs"] + build_ltfs() + upload_ltfs() + +before_build() + +#на всякий случай +#cd data_svelte/ +#curl.exe -F "file=@edit.htm.gz;filename=edit.htm.gz" http://192.168.2.110/edit \ No newline at end of file diff --git a/tools/lt_fsflash.py b/tools/lt_fsflash.py new file mode 100644 index 00000000..1f71fa6b --- /dev/null +++ b/tools/lt_fsflash.py @@ -0,0 +1,34 @@ + +# c:\Users\bmw\.platformio\penv\Scripts\pio run -t buildfs +#c:\Users\bmw\.platformio\penv\.libretiny\Scripts\ltchiptool -v flash write lt_littlefs.bin --start 0x1db000 -f BK7231N -c + + + +Import("env") + +# Определите имя таргета +target_name = "flashfs" + +# Получите путь к выходному файлу LittleFS из переменной среды env +output_file = env.subst("$PROJECT_DIR") + "/lt_littlefs.bin" + +# Получите путь к mklittlefs.exe из переменной среды env +ltchiptool_path = "${LTCHIPTOOL}" +# Определите команду, которая будет выполнена при вызове таргета +command = "{} flash write {} -s 0x1db000 -c -f BK7231N ".format(ltchiptool_path, output_file) + +# Добавьте кастомный таргет +env.AddCustomTarget( + name=target_name, + dependencies=None, + actions=[command], + title="Flash LittleFS", + description="Flash LittleFS file system" +) + + +# Определите путь к исполняемому файлу ltchiptool + + + +print("Custom target '{}' added!".format(target_name)) \ No newline at end of file diff --git a/tools/partitions_custom_8mb.csv b/tools/partitions_custom_8mb.csv new file mode 100644 index 00000000..1c76ee59 --- /dev/null +++ b/tools/partitions_custom_8mb.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, , 3000K, +app1, app, ota_1, , 3000K, +spiffs, data, spiffs, , 1500K, \ No newline at end of file diff --git a/tools/patch32_ws.py b/tools/patch32_ws.py index 4800c237..c0914f99 100644 --- a/tools/patch32_ws.py +++ b/tools/patch32_ws.py @@ -3,25 +3,33 @@ # #define WIFI_CLIENT_MAX_WRITE_RETRY (10) # #define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000) # Прописать скрипт в platformio.ini внутри [env:esp32_4mb3f] написать extra_scripts = pre:tools/patch32_ws.py - +Import("env") import os import shutil from sys import platform -if platform == "linux" or platform == "linux2": +pio_home = env.subst("$PROJECT_CORE_DIR") +print("PLATFORMIO_DIR" + pio_home) + +if platform == "linux" or platform == "linux2" or platform == "darwin": # linux - mainPyPath = '/home/rise/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiClient.cpp' + #mainPyPath = '/home/rise/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiClient.cpp' + mainPyPath = pio_home + '/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiClient.cpp' else: # windows - mainPyPath = os.environ['USERPROFILE'] + '\\.platformio\\packages\\framework-arduinoespressif32\\libraries\\WiFi\\src\\WiFiClient.cpp' + #mainPyPath = os.environ['USERPROFILE'] + '\\.platformio\\packages\\framework-arduinoespressif32\\libraries\\WiFi\\src\\WiFiClient.cpp' + mainPyPath = pio_home + '\\packages\\framework-arduinoespressif32\\libraries\\WiFi\\src\\WiFiClient.cpp' # print(mainPyPath) - -with open(mainPyPath) as fr: - oldData = fr.read() - if not 'if WIFI_CLIENT_MAX_WRITE_RETRY (10)' in oldData: - shutil.copyfile(mainPyPath, mainPyPath+'.bak') - newData = oldData.replace('#define WIFI_CLIENT_MAX_WRITE_RETRY (10)', '#define WIFI_CLIENT_MAX_WRITE_RETRY (2)') - newData = newData.replace('#define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000)', '#define WIFI_CLIENT_SELECT_TIMEOUT_US (500000)') - with open(mainPyPath, 'w') as fw: - fw.write(newData) \ No newline at end of file +try: + with open(mainPyPath) as fr: + oldData = fr.read() + if not 'if WIFI_CLIENT_MAX_WRITE_RETRY (10)' in oldData: + shutil.copyfile(mainPyPath, mainPyPath+'.bak') + newData = oldData.replace('#define WIFI_CLIENT_MAX_WRITE_RETRY (10)', '#define WIFI_CLIENT_MAX_WRITE_RETRY (5)') + newData = newData.replace('#define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000)', '#define WIFI_CLIENT_SELECT_TIMEOUT_US (500000)') + with open(mainPyPath, 'w') as fw: + fw.write(newData) + print(f"Файл изменён, ОК! {mainPyPath}") +except FileNotFoundError: + print("Файл не найден или не удается открыть") \ No newline at end of file diff --git a/tools/patch32c6.py b/tools/patch32c6.py new file mode 100644 index 00000000..e1d569e7 --- /dev/null +++ b/tools/patch32c6.py @@ -0,0 +1,42 @@ +Import("env") +import json +import os +import shutil +from sys import platform + +pio_home = env.subst("$PROJECT_CORE_DIR") +print("PLATFORMIO_DIR" + pio_home) + +if platform == "linux" or platform == "linux2" or platform == "darwin": + # linux + #devkitm = '/home/rise/.platformio/platforms/espressif32/boards/esp32-c6-devkitm-1.json' + #devkitc = '/home/rise/.platformio/platforms/espressif32/boards/esp32-c6-devkitc-1.json' + devkitm = pio_home + '/platforms/espressif32/boards/esp32-c6-devkitm-1.json' + devkitc = pio_home + '/platforms/espressif32/boards/esp32-c6-devkitc-1.json' +else: + # windows + devkitm = pio_home + '\\platforms\\espressif32\\boards\\esp32-c6-devkitm-1.json' + devkitc = pio_home + '\\platforms\\espressif32\\boards\\esp32-c6-devkitc-1.json' + +def add_arduino_to_frameworks(file_name): + try: + with open(file_name, 'r+') as f: + data = json.load(f) + frameworks = data['frameworks'] + if 'arduino' not in frameworks: + frameworks.insert(frameworks.index('espidf') + 1, 'arduino') + data['frameworks'] = frameworks + f.seek(0) + json.dump(data, f, indent=4) + f.truncate() + print(f"Файл изменён, ОК! {file_name}") + else: + print(f"Arduino already exists in {file_name}") + except FileNotFoundError: + print("Файл не найден или не удается открыть") + +if os.path.exists(devkitm) and os.path.exists(devkitc): + add_arduino_to_frameworks(devkitm) + add_arduino_to_frameworks(devkitc) +else: + print("One or both files do not exist.") \ No newline at end of file diff --git a/tools/patch8266_16m.py b/tools/patch8266_16m.py index 6b4b139c..c6426761 100644 --- a/tools/patch8266_16m.py +++ b/tools/patch8266_16m.py @@ -1,23 +1,31 @@ # правим %USERPROFILE%\.platformio\platforms\espressif8266\builder\main.py 103-115 # для добавления возможности прошивки 16мб модуля esp8266 +Import("env") import os import shutil from sys import platform -if platform == "linux" or platform == "linux2": - # linux - mainPyPath = '/home/rise/.platformio/platforms/espressif8266@4.0.1/builder/main.py' +pio_home = env.subst("$PROJECT_CORE_DIR") +print("PLATFORMIO_DIR" + pio_home) + +if platform == "linux" or platform == "linux2" or platform == "darwin": + #mainPyPath = '/home/rise/.platformio/platforms/espressif8266@4.0.1/builder/main.py' + mainPyPath = pio_home + '/platforms/espressif8266@4.0.1/builder/main.py' else: - # windows - mainPyPath = os.environ['USERPROFILE'] + '\\.platformio\\platforms\\espressif8266@4.0.1\\builder\\main.py' + #mainPyPath = os.environ['USERPROFILE'] + '\\.platformio\\platforms\\espressif8266@4.0.1\\builder\\main.py' + mainPyPath = pio_home + '\\platforms\\espressif8266@4.0.1\\builder\\main.py' -# print(mainPyPath) +print("FIX 16Mb path: " + mainPyPath) -with open(mainPyPath) as fr: - oldData = fr.read() - if not 'if _value == -0x6000:' in oldData: - shutil.copyfile(mainPyPath, mainPyPath+'.bak') - newData = oldData.replace('_value += 0xE00000 # correction', '_value += 0xE00000 # correction\n\n if _value == -0x6000:\n _value = env[k]-0x40200000') - with open(mainPyPath, 'w') as fw: - fw.write(newData) +try: + with open(mainPyPath) as fr: + oldData = fr.read() + if not 'if _value == -0x6000:' in oldData: + shutil.copyfile(mainPyPath, mainPyPath+'.bak') + newData = oldData.replace('_value += 0xE00000 # correction', '_value += 0xE00000 # correction\n\n if _value == -0x6000:\n _value = env[k]-0x40200000') + with open(mainPyPath, 'w') as fw: + fw.write(newData) + print(f"Файл изменён, ОК! {mainPyPath}") +except FileNotFoundError: + print("Файл не найден или не удается открыть")