diff --git a/.gitignore b/.gitignore index 65bd8853..41f063ef 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +/myProfile_wm.json +/myProfile.json data_svelte/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 61cd5f78..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "files.associations": { - "array": "cpp", - "deque": "cpp", - "list": "cpp", - "string": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "string_view": "cpp", - "initializer_list": "cpp", - "ranges": "cpp", - "thread": "cpp" - } -} \ No newline at end of file diff --git a/PrepareProject.py b/PrepareProject.py index 6f592233..02731c09 100644 --- a/PrepareProject.py +++ b/PrepareProject.py @@ -13,7 +13,11 @@ # python PrepareProject.py --profile <ИмяФайла> # python PrepareProject.py -p <ИмяФайла> # -# +# поддерживаемые контроллеры (профили): +# esp8266_4mb +# esp32_4mb +# esp8266_1mb +# esp8266_1mb_ota import configparser import os, json, sys, getopt @@ -98,8 +102,7 @@ else: # устанавливаем параметры сборки profJson['projectProp'] = { 'platformio': { - 'default_envs': 'esp8266_4mb', - 'data_dir': 'data_svelte' + 'default_envs': 'esp8266_4mb' } } # загружаем список модулей для сборки @@ -107,23 +110,35 @@ else: # сохраняем новый профиль 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'] + +# назначаем папку с файлами прошивки в зависимости от устройства и запоминаем в профиле +dataDir = 'data_svelte' +if deviceName == 'esp8266_1mb_ota': + dataDir = 'data_svelte_lite' +profJson['projectProp'] = { + 'platformio': { + 'data_dir': dataDir + } +} # генерируем файлы проекта на основе подготовленного профиля # заполняем конфигурационный файл прошивки параметрами из профиля -with open("data_svelte/settings.json", "r", encoding='utf-8') as read_file: +with open(dataDir + "/settings.json", "r", encoding='utf-8') as read_file: iotmJson = json.load(read_file) for key, value in profJson['iotmSettings'].items(): iotmJson[key] = value -with open("data_svelte/settings.json", "w", encoding='utf-8') as write_file: +with open(dataDir + "/settings.json", "w", encoding='utf-8') as write_file: json.dump(iotmJson, write_file, ensure_ascii=False, indent=4, sort_keys=False) -# определяем какое устройство используется в профиле -deviceName = profJson['projectProp']['platformio']['default_envs'] + # собираем меню прошивки из модулей # параллельно формируем список имен активных модулей -# параллельно собираем необходимые активным модулям библиотеки для включения в компиляцию для текущего типа устройства (esp8266_4m, esp32_4mb) +# параллельно собираем необходимые активным модулям библиотеки для включения в компиляцию для текущего типа устройства (esp8266_4m, esp32_4mb, esp8266_1m, esp8266_1m_ota) activeModulesName = [] # список имен активных модулей allLibs = "" # подборка всех библиотек необходимых модулям для дальнейшей записи в конфигурацию platformio itemsCount = 1; @@ -145,7 +160,7 @@ for section, modules in profJson['modules'].items(): configItemsJson['name'] = str(itemsCount) + ". " + configItemsJson['name'] itemsCount = itemsCount + 1 itemsJson.append(configItemsJson) -with open("data_svelte/items.json", "w", encoding='utf-8') as write_file: +with open(dataDir + "/items.json", "w", encoding='utf-8') as write_file: json.dump(itemsJson, write_file, ensure_ascii=False, indent=4, sort_keys=False) diff --git a/data_svelte/build/bundle.css.gz b/data_svelte/build/bundle.css.gz index 54bc75e4..7c46e54f 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 271fc795..38e4d750 100644 Binary files a/data_svelte/build/bundle.js.gz and b/data_svelte/build/bundle.js.gz differ diff --git a/data_svelte/index.html b/data_svelte/index.html index b4164705..77e5d2fb 100644 --- a/data_svelte/index.html +++ b/data_svelte/index.html @@ -4,12 +4,12 @@ - IoT Manager 4.4.0 + IoT Manager 4.4.2 - + - + diff --git a/data_svelte/items.json b/data_svelte/items.json index 74a8c083..218963dd 100644 --- a/data_svelte/items.json +++ b/data_svelte/items.json @@ -7,34 +7,65 @@ "header": "Виртуальные элементы" }, { - "name": "1. График", + "global": 0, + "name": "1. Будильник (Cron)", + "type": "Writing", + "subtype": "Cron", + "id": "cron", + "widget": "anydataDef", + "page": "Таймеры", + "descr": "Будильник", + "int": 1, + "val": "*/15 * * * * *", + "formatNextAlarm": "%H:%M:%S", + "needSave": 0, + "num": 1 + }, + { + "global": 0, + "name": "2. График", "type": "Writing", "subtype": "Loging", "id": "log", "widget": "chart2", "page": "Графики", "descr": "Температура", - "num": 1, + "num": 2, "int": 5, "logid": "t", "points": 300 }, { - "name": "2. График дневного расхода", + "global": 0, + "name": "3. График по событию", + "type": "Writing", + "subtype": "Loging", + "id": "log", + "widget": "chart2", + "page": "Графики", + "descr": "Температура", + "int": 0, + "num": 3, + "points": 300 + }, + { + "global": 0, + "name": "4. График дневного расхода", "type": "Writing", "subtype": "LogingDaily", "id": "log", "widget": "chart3", "page": "Графики", "descr": "Температура", - "num": 2, + "num": 4, "int": 1, "logid": "t", "points": 365, - "test": 0 + "column": 0 }, { - "name": "3. Таймер", + "global": 0, + "name": "5. Таймер", "type": "Writing", "subtype": "Timer", "id": "timer", @@ -46,73 +77,87 @@ "ticker": 1, "repeat": 1, "needSave": 0, - "num": 3 + "num": 5 }, { - "name": "4. Окно ввода числа (переменная)", + "global": 0, + "name": "6. Окно ввода числа (переменная)", "type": "Reading", "subtype": "Variable", "id": "value", + "needSave": 0, "widget": "inputDgt", "page": "Ввод", "descr": "Введите число", "int": "0", "val": "0.0", - "num": 4 + "map": "1024,1024,1,100", + "plus": 0, + "multiply": 1, + "round": 0, + "num": 6 }, { - "name": "5. Окно ввода времени", + "global": 0, + "name": "7. Окно ввода времени", "type": "Reading", "subtype": "Variable", "id": "time", + "needSave": 0, "widget": "inputTm", "page": "Ввод", "descr": "Введите время", "int": "0", "val": "02:00", - "num": 5 + "num": 7 }, { - "name": "6. Окно ввода даты", + "global": 0, + "name": "8. Окно ввода даты", "type": "Reading", "subtype": "Variable", "id": "time", + "needSave": 0, "widget": "inputDate", "page": "Ввод", "descr": "Введите дату", "int": "0", "val": "24.05.2022", - "num": 6 + "num": 8 }, { - "name": "7. Окно ввода текста", + "global": 0, + "name": "9. Окно ввода текста", "type": "Reading", "subtype": "Variable", "id": "txt", + "needSave": 0, "widget": "inputTxt", "page": "Ввод", "descr": "Введите текст", "int": "0", "val": "текст", - "num": 7 + "num": 9 }, { - "name": "8. Виртуальная кнопка", + "global": 0, + "name": "10. Виртуальная кнопка", "type": "Reading", "subtype": "VButton", "id": "vbtn", + "needSave": 0, "widget": "toggle", "page": "Кнопки", "descr": "Кнопка", "int": "0", "val": "0", - "num": 8 + "num": 10 }, { "header": "Сенсоры" }, { - "name": "9. Acs712 Ток", + "name": "11. Acs712 Ток", "type": "Reading", "subtype": "Acs712", "id": "amp", @@ -122,10 +167,11 @@ "round": 3, "pin": 39, "int": 5, - "num": 9 + "num": 11 }, { - "name": "10. AHTXX Температура", + "global": 0, + "name": "12. AHTXX Температура", "type": "Reading", "subtype": "AhtXXt", "id": "Temp20", @@ -136,10 +182,11 @@ "addr": "0x38", "shtType": 1, "round": 1, - "num": 10 + "num": 12 }, { - "name": "11. AHTXX Влажность", + "global": 0, + "name": "13. AHTXX Влажность", "type": "Reading", "subtype": "AhtXXh", "id": "Hum20", @@ -150,10 +197,11 @@ "addr": "0x38", "shtType": 1, "round": 1, - "num": 11 + "num": 13 }, { - "name": "12. Аналоговый сенсор", + "global": 0, + "name": "14. Аналоговый сенсор", "type": "Reading", "subtype": "AnalogAdc", "id": "t", @@ -167,10 +215,11 @@ "pin": 0, "int": 15, "avgSteps": 1, - "num": 12 + "num": 14 }, { - "name": "13. BME280 Температура", + "global": 0, + "name": "15. BME280 Температура", "type": "Reading", "subtype": "Bme280t", "id": "tmp3", @@ -180,10 +229,11 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 13 + "num": 15 }, { - "name": "14. BME280 Давление", + "global": 0, + "name": "16. BME280 Давление", "type": "Reading", "subtype": "Bme280p", "id": "Press3", @@ -193,10 +243,11 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 14 + "num": 16 }, { - "name": "15. BME280 Влажность", + "global": 0, + "name": "17. BME280 Влажность", "type": "Reading", "subtype": "Bme280h", "id": "Hum3", @@ -206,10 +257,11 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 15 + "num": 17 }, { - "name": "16. BMP280 Температура", + "global": 0, + "name": "18. BMP280 Температура", "type": "Reading", "subtype": "Bmp280t", "id": "tmp3", @@ -219,10 +271,11 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 16 + "num": 18 }, { - "name": "17. BMP280 Давление", + "global": 0, + "name": "19. BMP280 Давление", "type": "Reading", "subtype": "Bmp280p", "id": "Press3", @@ -232,10 +285,11 @@ "int": 15, "addr": "0x77", "round": 1, - "num": 17 + "num": 19 }, { - "name": "18. DHT11 Температура", + "global": 0, + "name": "20. DHT11 Температура", "type": "Reading", "subtype": "Dht1122t", "id": "tmp3", @@ -245,10 +299,11 @@ "int": 15, "pin": 0, "senstype": "dht11", - "num": 18 + "num": 20 }, { - "name": "19. DHT11 Влажность", + "global": 0, + "name": "21. DHT11 Влажность", "type": "Reading", "subtype": "Dht1122h", "id": "Hum3", @@ -258,10 +313,11 @@ "int": 15, "pin": 0, "senstype": "dht11", - "num": 19 + "num": 21 }, { - "name": "20. DS18B20 Температура", + "global": 0, + "name": "22. DS18B20 Температура", "type": "Reading", "subtype": "Ds18b20", "id": "dstmp", @@ -273,61 +329,11 @@ "index": 0, "addr": "", "round": 1, - "num": 20 - }, - { - "name": "21. Частотомер на ADC, Частота", - "type": "Reading", - "subtype": "FreqMeterF", - "id": "freq", - "widget": "anydataHtz", - "page": "Частотомер", - "descr": "Частота", - "plus": 0, - "multiply": 1, - "round": 1, - "pin-Esp32": 33, - "samples": 512, - "samplingFrequency": 5000, - "int": 5, - "num": 21 - }, - { - "name": "22. Частотомер на ADC, Процент Пульсации", - "type": "Reading", - "subtype": "FreqMeterPcFl", - "id": "pctFlicker", - "widget": "anydataPct", - "page": "Частотомер", - "descr": "Процент Пульсации", - "plus": 0, - "multiply": 1, - "round": 1, - "pin-Esp32": 33, - "samples": 512, - "samplingFrequency": 5000, - "int": 5, "num": 22 }, { - "name": "23. Частотомер на ADC, Индекс Пульсации", - "type": "Reading", - "subtype": "FreqMeterFlIn", - "id": "flickerIndex", - "widget": "anydataDef", - "page": "Частотомер", - "descr": "Индекс Пульсации", - "plus": 0, - "multiply": 1, - "round": 10, - "pin-Esp32": 33, - "samples": 512, - "samplingFrequency": 5000, - "int": 5, - "num": 23 - }, - { - "name": "24. GY21 Температура", + "global": 0, + "name": "23. GY21 Температура", "type": "Reading", "subtype": "GY21t", "id": "tmp4", @@ -336,10 +342,11 @@ "descr": "Температура", "round": 1, "int": 15, - "num": 24 + "num": 23 }, { - "name": "25. GY21 Влажность", + "global": 0, + "name": "24. GY21 Влажность", "type": "Reading", "subtype": "GY21h", "id": "Hum4", @@ -348,10 +355,11 @@ "descr": "Влажность", "round": 1, "int": 15, - "num": 25 + "num": 24 }, { - "name": "26. HDC1080 Температура", + "global": 0, + "name": "25. HDC1080 Температура", "type": "Reading", "subtype": "Hdc1080t", "id": "Temp1080", @@ -361,10 +369,11 @@ "int": 15, "addr": "0x40", "round": 1, - "num": 26 + "num": 25 }, { - "name": "27. HDC1080 Влажность", + "global": 0, + "name": "26. HDC1080 Влажность", "type": "Reading", "subtype": "Hdc1080h", "id": "Hum1080", @@ -374,10 +383,11 @@ "int": 15, "addr": "0x40", "round": 1, - "num": 27 + "num": 26 }, { - "name": "28. MAX6675 Температура", + "global": 0, + "name": "27. MAX6675 Температура", "type": "Reading", "subtype": "Max6675t", "id": "maxtmp", @@ -388,10 +398,11 @@ "DO": 12, "CS": 13, "CLK": 14, - "num": 28 + "num": 27 }, { - "name": "29. PZEM 004t Напряжение", + "global": 0, + "name": "28. PZEM 004t Напряжение", "type": "Reading", "subtype": "Pzem004v", "id": "v", @@ -401,10 +412,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 29 + "num": 28 }, { - "name": "30. PZEM 004t Сила тока", + "global": 0, + "name": "29. PZEM 004t Сила тока", "type": "Reading", "subtype": "Pzem004a", "id": "a", @@ -414,10 +426,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 30 + "num": 29 }, { - "name": "31. PZEM 004t Мощность", + "global": 0, + "name": "30. PZEM 004t Мощность", "type": "Reading", "subtype": "Pzem004w", "id": "w", @@ -427,10 +440,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 31 + "num": 30 }, { - "name": "32. PZEM 004t Энергия", + "global": 0, + "name": "31. PZEM 004t Энергия", "type": "Reading", "subtype": "Pzem004wh", "id": "wh", @@ -440,10 +454,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 32 + "num": 31 }, { - "name": "33. PZEM 004t Частота", + "global": 0, + "name": "32. PZEM 004t Частота", "type": "Reading", "subtype": "Pzem004hz", "id": "hz", @@ -453,10 +468,11 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 33 + "num": 32 }, { - "name": "34. PZEM 004t Косинус", + "global": 0, + "name": "33. PZEM 004t Косинус", "type": "Reading", "subtype": "Pzem004pf", "id": "pf", @@ -466,11 +482,12 @@ "int": 15, "addr": "0xF8", "round": 1, - "num": 34 + "num": 33 }, { - "name": "35. Сканер кнопок 433 MHz", - "num": 35, + "global": 0, + "name": "34. Сканер кнопок 433 MHz", + "num": 34, "type": "Reading", "subtype": "RCswitch", "id": "rsw", @@ -479,7 +496,8 @@ "pinTx": 12 }, { - "name": "36. Sht20 Температура", + "global": 0, + "name": "35. Sht20 Температура", "type": "Reading", "subtype": "Sht20t", "id": "tmp2", @@ -488,10 +506,11 @@ "descr": "Температура", "int": 15, "round": 1, - "num": 36 + "num": 35 }, { - "name": "37. Sht20 Влажность", + "global": 0, + "name": "36. Sht20 Влажность", "type": "Reading", "subtype": "Sht20h", "id": "Hum2", @@ -500,10 +519,11 @@ "descr": "Влажность", "int": 15, "round": 1, - "num": 37 + "num": 36 }, { - "name": "38. Sht30 Температура", + "global": 0, + "name": "37. Sht30 Температура", "type": "Reading", "subtype": "Sht30t", "id": "tmp30", @@ -512,10 +532,11 @@ "descr": "SHT30 Температура", "int": 15, "round": 1, - "num": 38 + "num": 37 }, { - "name": "39. Sht30 Влажность", + "global": 0, + "name": "38. Sht30 Влажность", "type": "Reading", "subtype": "Sht30h", "id": "Hum30", @@ -524,11 +545,12 @@ "descr": "SHT30 Влажность", "int": 15, "round": 1, - "num": 39 + "num": 38 }, { - "name": "40. HC-SR04 Ультразвуковой дальномер", - "num": 40, + "global": 0, + "name": "39. HC-SR04 Ультразвуковой дальномер", + "num": 39, "type": "Reading", "subtype": "Sonar", "id": "sonar", @@ -540,7 +562,7 @@ "int": 5 }, { - "name": "41. UART", + "name": "40. UART", "type": "Reading", "subtype": "UART", "page": "", @@ -549,32 +571,38 @@ "id": "u", "tx": 12, "rx": 13, + "line": 2, "speed": 9600, - "num": 41 + "eventFormat": 0, + "num": 40 }, { "header": "Исполнительные устройства" }, { - "name": "42. Кнопка подключенная к пину", + "global": 0, + "name": "41. Кнопка подключенная к пину", "type": "Writing", "subtype": "ButtonIn", "id": "btn", "widget": "toggle", "page": "Кнопки", "descr": "Освещение", + "needSave": 0, "int": 0, "pin": 16, "execLevel": "1", "pinMode": "INPUT", "debounceDelay": 50, "fixState": 0, - "num": 42 + "num": 41 }, { - "name": "43. Управление пином", + "global": 0, + "name": "42. Управление пином", "type": "Writing", "subtype": "ButtonOut", + "needSave": 0, "id": "btn", "widget": "toggle", "page": "Кнопки", @@ -582,10 +610,11 @@ "int": 0, "inv": 0, "pin": 2, - "num": 43 + "num": 42 }, { - "name": "44. Сервопривод", + "global": 0, + "name": "43. Сервопривод", "type": "Writing", "subtype": "IoTServo", "id": "servo", @@ -596,10 +625,11 @@ "pin": 12, "apin": -1, "amap": "0, 4096, 0, 180", - "num": 44 + "num": 43 }, { - "name": "45. Расширитель портов Mcp23017", + "global": 0, + "name": "44. Расширитель портов Mcp23017", "type": "Reading", "subtype": "Mcp23017", "id": "Mcp", @@ -609,10 +639,11 @@ "int": "0", "addr": "0x20", "index": 1, - "num": 45 + "num": 44 }, { - "name": "46. MP3 плеер", + "global": 0, + "name": "45. MP3 плеер", "type": "Reading", "subtype": "Mp3", "id": "mp3", @@ -622,9 +653,28 @@ "int": 1, "pins": "14,12", "volume": 20, + "num": 45 + }, + { + "global": 0, + "name": "46. Сенсорная кнопка", + "type": "Writing", + "subtype": "Multitouch", + "id": "impulse", + "widget": "anydataDef", + "page": "Кнопки", + "descr": "Количество нажаний", + "needSave": 0, + "int": 300, + "inv": 1, + "pin": 16, + "pinMode": "INPUT", + "debounceDelay": 50, + "PWMDelay": 500, "num": 46 }, { + "global": 0, "name": "47. Расширитель портов Pcf8574", "type": "Reading", "subtype": "Pcf8574", @@ -638,6 +688,7 @@ "num": 47 }, { + "global": 0, "name": "48. PWM ESP8266", "type": "Writing", "subtype": "Pwm8266", @@ -653,6 +704,7 @@ "num": 48 }, { + "global": 0, "name": "49. Телеграм-Лайт", "type": "Writing", "subtype": "TelegramLT", @@ -668,6 +720,7 @@ "header": "Экраны" }, { + "global": 0, "name": "50. LCD экран 2004", "type": "Reading", "subtype": "Lcd2004", @@ -696,5 +749,25 @@ "coord": "0,0", "id2show": "id датчика", "num": 51 + }, + { + "global": 0, + "name": "52. Strip ws2812b", + "type": "Reading", + "subtype": "Ws2812b", + "id": "strip", + "widget": "range", + "page": "Кнопки", + "descr": "Лента", + "int": 15, + "needSave": 0, + "pin": "4", + "numLeds": "8", + "brightness": "100", + "mode": "1", + "min": "15", + "max": "30", + "idshow": "t", + "num": 52 } ] \ No newline at end of file diff --git a/data_svelte/settings.json b/data_svelte/settings.json index f2b7d36c..b7707bf7 100644 --- a/data_svelte/settings.json +++ b/data_svelte/settings.json @@ -1,5 +1,4 @@ { - "settings_": "", "name": "IoTmanagerVer4", "apssid": "IoTmanager", "appass": "", @@ -17,7 +16,10 @@ "serverip": "http://iotmanager.org", "log": 0, "mqttin": 0, + "i2c": 0, "pinSCL": 0, "pinSDA": 0, - "i2cFreq": 100000 + "i2cFreq": 100000, + "wg": "group1", + "settings_": "" } \ No newline at end of file diff --git a/data_svelte/values.json b/data_svelte/values.json new file mode 100644 index 00000000..0e0dcd23 --- /dev/null +++ b/data_svelte/values.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/data_svelte/widgets.json b/data_svelte/widgets.json index cab0dbe2..4c681bff 100644 --- a/data_svelte/widgets.json +++ b/data_svelte/widgets.json @@ -47,7 +47,7 @@ "name": "anydataWth", "label": "Энергия", "widget": "anydata", - "after": "kWt/Hr", + "after": "kWh", "icon": "speedometer" }, { @@ -209,6 +209,27 @@ "after": "ppm", "icon": "speedometer" }, + { + "name": "anydatamAmp", + "label": "миллиАмперы", + "widget": "anydata", + "after": "mAmp", + "icon": "speedometer" + }, + { + "name": "anydatamVlt", + "label": "миллиВольты", + "widget": "anydata", + "after": "mVlt", + "icon": "speedometer" + }, + { + "name": "anydatamWt", + "label": "миллиВатты", + "widget": "anydata", + "after": "mWt", + "icon": "speedometer" + }, { "name": "nil", "label": "Без виджета" diff --git a/data_svelte_lite/config.json b/data_svelte_lite/config.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/data_svelte_lite/config.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/data_svelte_lite/dev_conf.txt b/data_svelte_lite/dev_conf.txt new file mode 100644 index 00000000..e69de29b diff --git a/data_svelte_lite/edit.htm.gz b/data_svelte_lite/edit.htm.gz new file mode 100644 index 00000000..d41d10c1 Binary files /dev/null and b/data_svelte_lite/edit.htm.gz differ diff --git a/data_svelte_lite/index.html b/data_svelte_lite/index.html new file mode 100644 index 00000000..38ef3291 --- /dev/null +++ b/data_svelte_lite/index.html @@ -0,0 +1,41 @@ + + + + + + + + IoT Manager 4.4.1 + + + + + + +
+
+

Настройка WiFi


+
+ +

+
+ +


+ +
+
+ + diff --git a/data_svelte_lite/items.json b/data_svelte_lite/items.json new file mode 100644 index 00000000..fd9eb626 --- /dev/null +++ b/data_svelte_lite/items.json @@ -0,0 +1,243 @@ +[ + { + "name": "Выберите элемент", + "num": 0 + }, + { + "header": "Виртуальные элементы" + }, + { + "name": "1. Таймер", + "type": "Writing", + "subtype": "Timer", + "id": "timer", + "widget": "anydataDef", + "page": "Таймеры", + "descr": "Таймер", + "int": 1, + "countDown": 15, + "ticker": 1, + "repeat": 1, + "needSave": 0, + "num": 1 + }, + { + "name": "2. Окно ввода числа (переменная)", + "type": "Reading", + "subtype": "Variable", + "id": "value", + "widget": "inputDgt", + "page": "Ввод", + "descr": "Введите число", + "int": "0", + "val": "0.0", + "num": 2 + }, + { + "name": "3. Окно ввода времени", + "type": "Reading", + "subtype": "Variable", + "id": "time", + "widget": "inputTm", + "page": "Ввод", + "descr": "Введите время", + "int": "0", + "val": "02:00", + "num": 3 + }, + { + "name": "4. Окно ввода даты", + "type": "Reading", + "subtype": "Variable", + "id": "time", + "widget": "inputDate", + "page": "Ввод", + "descr": "Введите дату", + "int": "0", + "val": "24.05.2022", + "num": 4 + }, + { + "name": "5. Окно ввода текста", + "type": "Reading", + "subtype": "Variable", + "id": "txt", + "widget": "inputTxt", + "page": "Ввод", + "descr": "Введите текст", + "int": "0", + "val": "текст", + "num": 5 + }, + { + "name": "6. Виртуальная кнопка", + "type": "Reading", + "subtype": "VButton", + "id": "vbtn", + "widget": "toggle", + "page": "Кнопки", + "descr": "Кнопка", + "int": "0", + "val": "0", + "num": 6 + }, + { + "header": "Сенсоры" + }, + { + "name": "7. DS18B20 Температура", + "type": "Reading", + "subtype": "Ds18b20", + "id": "dstmp", + "widget": "anydataTmp", + "page": "Сенсоры", + "descr": "DS Температура", + "int": 15, + "pin": 2, + "index": 0, + "addr": "", + "round": 1, + "num": 7 + }, + { + "name": "8. Сканер кнопок 433 MHz", + "num": 8, + "type": "Reading", + "subtype": "RCswitch", + "id": "rsw", + "int": 500, + "pinRx": 12, + "pinTx": 12 + }, + { + "name": "9. HC-SR04 Ультразвуковой дальномер", + "num": 9, + "type": "Reading", + "subtype": "Sonar", + "id": "sonar", + "widget": "anydataTmp", + "page": "Сенсоры", + "descr": "Расстояние (см)", + "pinTrig": 5, + "pinEcho": 4, + "int": 5 + }, + { + "header": "Исполнительные устройства" + }, + { + "name": "10. Кнопка подключенная к пину", + "type": "Writing", + "subtype": "ButtonIn", + "id": "btn", + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "int": 0, + "pin": 16, + "execLevel": "1", + "pinMode": "INPUT", + "debounceDelay": 50, + "fixState": 0, + "num": 10 + }, + { + "name": "11. Управление пином", + "type": "Writing", + "subtype": "ButtonOut", + "id": "btn", + "widget": "toggle", + "page": "Кнопки", + "descr": "Освещение", + "int": 0, + "inv": 0, + "pin": 2, + "num": 11 + }, + { + "name": "12. Расширитель портов Mcp23017", + "type": "Reading", + "subtype": "Mcp23017", + "id": "Mcp", + "widget": "", + "page": "", + "descr": "", + "int": "0", + "addr": "0x20", + "index": 1, + "num": 12 + }, + { + "name": "13. Расширитель портов Pcf8574", + "type": "Reading", + "subtype": "Pcf8574", + "id": "Pcf", + "widget": "", + "page": "", + "descr": "", + "int": "0", + "addr": "0x20", + "index": 1, + "num": 13 + }, + { + "name": "14. PWM ESP8266", + "type": "Writing", + "subtype": "Pwm8266", + "id": "pwm", + "widget": "range", + "page": "Кнопки", + "descr": "PWM", + "int": 0, + "pin": 15, + "freq": 5000, + "val": 0, + "apin": -1, + "num": 14 + }, + { + "name": "15. Телеграм-Лайт", + "type": "Writing", + "subtype": "TelegramLT", + "id": "tg", + "widget": "", + "page": "", + "descr": "", + "token": "", + "chatID": "", + "num": 15 + }, + { + "header": "Экраны" + }, + { + "name": "16. LCD экран 2004", + "type": "Reading", + "subtype": "Lcd2004", + "id": "Lcd", + "widget": "", + "page": "", + "descr": "T", + "int": 15, + "addr": "0x27", + "size": "20,4", + "coord": "0,0", + "id2show": "id датчика", + "num": 16 + }, + { + "name": "17. LCD экран 1602", + "type": "Reading", + "subtype": "Lcd2004", + "id": "Lcd", + "widget": "", + "page": "", + "descr": "T", + "int": 15, + "addr": "0x27", + "size": "16,2", + "coord": "0,0", + "id2show": "id датчика", + "num": 17 + } +] \ No newline at end of file diff --git a/data_svelte_lite/scenario.txt b/data_svelte_lite/scenario.txt new file mode 100644 index 00000000..e69de29b diff --git a/data_svelte_lite/settings.json b/data_svelte_lite/settings.json new file mode 100644 index 00000000..e6d79494 --- /dev/null +++ b/data_svelte_lite/settings.json @@ -0,0 +1,24 @@ +{ + "name": "IoTmanagerVer4", + "apssid": "IoTmanager", + "appass": "", + "routerssid": "rise", + "routerpass": "hostel3333", + "timezone": 2, + "ntp": "pool.ntp.org", + "weblogin": "admin", + "webpass": "admin", + "mqttServer": "m2.wqtt.ru", + "mqttPort": 8021, + "mqttPrefix": "/risenew", + "mqttUser": "rise", + "mqttPass": "3hostel3", + "serverip": "http://iotmanager.org", + "log": 0, + "mqttin": 0, + "pinSCL": 0, + "pinSDA": 0, + "i2cFreq": 100000, + "settings_": "", + "wg": "group1" +} \ No newline at end of file diff --git a/data_svelte_lite/values.json b/data_svelte_lite/values.json new file mode 100644 index 00000000..0e0dcd23 --- /dev/null +++ b/data_svelte_lite/values.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/data_svelte_lite/widgets.json b/data_svelte_lite/widgets.json new file mode 100644 index 00000000..cab0dbe2 --- /dev/null +++ b/data_svelte_lite/widgets.json @@ -0,0 +1,216 @@ +[ + { + "name": "anydataRed", + "label": "Сообщение1", + "widget": "anydata", + "icon": "body", + "color": "red", + "descrColor": "red" + }, + { + "name": "anydataDgr", + "label": "Сообщение2", + "widget": "anydata", + "after": "", + "color": "red", + "icon": "walk" + }, + { + "name": "anydataDef", + "label": "Текст", + "widget": "anydata", + "after": "", + "icon": "" + }, + { + "name": "anydataVlt", + "label": "Вольты", + "widget": "anydata", + "after": "V", + "icon": "speedometer" + }, + { + "name": "anydataAmp", + "label": "Амперы", + "widget": "anydata", + "after": "A", + "icon": "speedometer" + }, + { + "name": "anydataWt", + "label": "Ватты", + "widget": "anydata", + "after": "Wt", + "icon": "speedometer" + }, + { + "name": "anydataWth", + "label": "Энергия", + "widget": "anydata", + "after": "kWt/Hr", + "icon": "speedometer" + }, + { + "name": "anydataHtz", + "label": "Герцы", + "widget": "anydata", + "after": "Hz", + "icon": "speedometer" + }, + { + "name": "anydataTmp", + "label": "Температура", + "widget": "anydata", + "after": "°С", + "icon": "thermometer" + }, + { + "name": "anydataMm", + "label": "Давление", + "widget": "anydata", + "after": "mm", + "icon": "speedometer" + }, + { + "name": "anydataHum", + "label": "Влажность", + "widget": "anydata", + "after": "%", + "icon": "water", + "color": "#88AADF" + }, + { + "name": "anydataTm", + "label": "Время", + "widget": "anydata", + "after": "", + "icon": "speedometer" + }, + { + "name": "button", + "label": "Кнопка", + "widget": "btn", + "size": "large", + "color": "green", + "send": "test" + }, + { + "name": "toggle", + "label": "Переключатель", + "widget": "toggle", + "icon": "", + "iconOff": "" + }, + { + "name": "chart1", + "label": "График без точек", + "widget": "chart", + "dateFormat": "HH:mm", + "maxCount": 86400, + "pointRadius": 0 + }, + { + "name": "chart2", + "label": "График с точками", + "widget": "chart", + "maxCount": 86400, + "dateFormat": "HH:mm" + }, + { + "name": "chart3", + "label": "График Дневной", + "widget": "chart", + "dateFormat": "DD.MM.YYYY", + "maxCount": 86400, + "type": "bar" + }, + { + "name": "fillgauge", + "label": "Бочка", + "widget": "fillgauge", + "circleColor": "#00FFFF", + "textColor": "#FFFFFF", + "waveTextColor": "#000000", + "waveColor": "#00FFFF" + }, + { + "name": "inputDate", + "label": "Ввод даты", + "widget": "input", + "size": "small", + "color": "orange", + "type": "date" + }, + { + "name": "inputDgt", + "label": "Ввод числа", + "widget": "input", + "color": "blue", + "type": "number" + }, + { + "name": "inputTxt", + "label": "Ввод текста", + "widget": "input", + "size": "small", + "color": "orange", + "type": "text" + }, + { + "name": "inputTm", + "label": "Ввод времени", + "widget": "input", + "color": "blue", + "type": "time" + }, + { + "name": "progressLine", + "label": "Статус линия", + "widget": "progress-line", + "icon": "sunny", + "max": "100", + "stroke": "10" + }, + { + "name": "progressRound", + "label": "Статус круг", + "widget": "progress-round", + "max": "100", + "stroke": "20", + "color": "#45ccce", + "background": "#777", + "semicircle": "1" + }, + { + "name": "range", + "label": "Ползунок", + "widget": "range", + "descrColor": "red", + "after": "%", + "k": 0.0977, + "min": 0, + "max": 100, + "debounce": 500 + }, + { + "name": "select", + "label": "Выпадающий", + "widget": "select", + "options": [ + "Выключен", + "Включен" + ], + "status": 0 + }, + { + "name": "anydataPpm", + "label": "PPM", + "widget": "anydata", + "after": "ppm", + "icon": "speedometer" + }, + { + "name": "nil", + "label": "Без виджета" + } +] \ No newline at end of file diff --git a/include/Buffers.h b/include/Buffers.h index 78026740..4e08a379 100644 --- a/include/Buffers.h +++ b/include/Buffers.h @@ -1,7 +1,7 @@ -#pragma once -#include "Global.h" -#include "MqttClient.h" +//#pragma once +//#include "Global.h" +//#include "MqttClient.h" -void eventGen2(String eventName, String eventValue); -extern void spaceCmdExecute(String &cmdStr); -extern String getValueJson(String &key); \ No newline at end of file +//void eventGen2(String eventName, String eventValue); +//extern void spaceCmdExecute(String &cmdStr); +//extern String getValueJson(String &key); \ No newline at end of file diff --git a/include/Const.h b/include/Const.h index bb82f3e8..ba4cd200 100644 --- a/include/Const.h +++ b/include/Const.h @@ -1,7 +1,15 @@ #pragma once //Версия прошивки -#define FIRMWARE_VERSION 430 +#define FIRMWARE_VERSION 432 + +#ifdef esp8266_1mb_ota +#define FIRMWARE_NAME "esp8266_1mb_ota" +#endif + +#ifdef esp8266_1mb +#define FIRMWARE_NAME "esp8266_1mb" +#endif #ifdef esp8266_4mb #define FIRMWARE_NAME "esp8266_4mb" @@ -13,6 +21,9 @@ //Размер буфера json #define JSON_BUFFER_SIZE 2048 +#define WEB_SOCKETS_FRAME_SIZE 2048 + +//#define LOOP_DEBUG //выбор сервера //#define ASYNC_WEB_SERVER @@ -29,13 +40,7 @@ #define TELEMETRY_UPDATE_INTERVAL_MIN 60 -#ifdef esp8266_4mb #define USE_LITTLEFS true -#endif - -#ifdef esp32_4mb -#define USE_LITTLEFS true -#endif #define START_DATETIME 1661990400 // 01.09.2022 00:00:00 константа для сокращения unix time @@ -48,8 +53,8 @@ enum TimerTask_t { WIFI_SCAN, TIME, TIME_SYNC, UPTIME, - UDP, // UDPP - TIMES, + UDP, // UDPP + TIMES, // периодические секундные проверки PTASK, ST, END }; diff --git a/include/EspFileSystem.h b/include/EspFileSystem.h index 5554dd49..81a6a660 100644 --- a/include/EspFileSystem.h +++ b/include/EspFileSystem.h @@ -38,6 +38,7 @@ extern void globalVarsSync(); extern String getParamsJson(); extern void syncSettingsFlashJson(); +extern void syncValuesFlashJson(); extern const String getChipId(); extern void setChipId(); diff --git a/include/Global.h b/include/Global.h index 558ace8f..fb0e535f 100644 --- a/include/Global.h +++ b/include/Global.h @@ -83,7 +83,9 @@ extern WebSocketsServer standWebSocket; ***********************************************глобальные переменные************************************************** **********************************************************************************************************************/ extern String settingsFlashJson; +extern String valuesFlashJson; extern String errorsHeapJson; +extern bool needSaveValues; // buf extern String orderBuf; @@ -137,6 +139,8 @@ extern Time_t _time_local; extern Time_t _time_utc; extern bool _time_isTrust; +//extern unsigned long loopPeriod; + // extern DynamicJsonDocument settingsFlashJsonDoc; // extern DynamicJsonDocument paramsFlashJsonDoc; // extern DynamicJsonDocument paramsHeapJsonDoc; diff --git a/include/PeriodicTasks.h b/include/PeriodicTasks.h index eee9d1d5..c9a27a9e 100644 --- a/include/PeriodicTasks.h +++ b/include/PeriodicTasks.h @@ -10,8 +10,5 @@ extern void periodicTasksInit(); extern void printGlobalVarSize(); -extern void handleError(String errorId, String errorValue); -extern void handleError(String errorId, int errorValue); - extern String ESP_getResetReason(void); extern String ESP32GetResetReason(uint32_t cpu_no); \ No newline at end of file diff --git a/include/WsServer.h b/include/WsServer.h index cc4116ef..89955c13 100644 --- a/include/WsServer.h +++ b/include/WsServer.h @@ -13,13 +13,9 @@ extern void hexdump(const void* mem, uint32_t len, uint8_t cols); #endif #endif -void sendFileToWs(String filename, int num, size_t frameSize); void publishStatusWs(const String& topic, const String& data); void publishChartWs(int num, String& path); void periodicWsSend(); -void sendStringToWs(const String& msg, uint8_t num, String name); -void publishChartToWs(String filename, int num, size_t frameSize, int maxCount, String id); -// void sendMark(const char* filename, const char* mark, uint8_t num); -// void sendFileToWs3(const String& filename, uint8_t num); -// void sendFileToWs4(const String& filename, uint8_t num); \ No newline at end of file +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); \ No newline at end of file diff --git a/include/classes/IoTItem.h b/include/classes/IoTItem.h index e16e536e..c87f925c 100644 --- a/include/classes/IoTItem.h +++ b/include/classes/IoTItem.h @@ -6,28 +6,31 @@ struct IoTValue { String valS = ""; bool isDecimal = true; - - uint8_t* extBinInfo = NULL; // дополнительные бинарные данные из модуля - size_t extBinInfoSize = 0; // размер дополнительных данных в байтах }; class IoTItem { public: - IoTItem(String parameters); + IoTItem(const String ¶meters); virtual ~IoTItem() {} virtual void loop(); virtual void doByInterval(); virtual IoTValue execute(String command, std::vector& param); - virtual void regEvent(String value, String consoleInfo); - virtual void regEvent(float value, String consoleInfo); + void checkIntFromNet(); + + virtual void regEvent(const String& value, const String& consoleInfo, bool error = false, bool genEvent = true); + virtual void regEvent(float value, const String& consoleInfo, bool error = false, bool genEvent = true); String getSubtype(); String getID(); + int getIntFromNet(); virtual String getValue(); - - void setInterval(unsigned long interval); + long getInterval(); + bool isGlobal(); + + void setInterval(long interval); + void setIntFromNet(int interval); unsigned long currentMillis; unsigned long prevMillis; @@ -35,15 +38,19 @@ class IoTItem { IoTValue value; // хранение основного значения, которое обновляется из сценария, execute(), loop() или doByInterval() - bool iAmDead = false; // признак необходимости удалить объект из базы + //bool iAmDead = false; // признак необходимости удалить объект из базы bool iAmLocal = true; // признак того, что айтем был создан локально - bool needSave = false; bool enableDoByInt = true; virtual IoTGpio* getGpioDriver(); - virtual void setValue(IoTValue Value); - virtual void setValue(String valStr); + virtual void setValue(const IoTValue& Value, bool genEvent = true); + virtual void setValue(const String& valStr, bool genEvent = true); + String getRoundValue(); + void getNetEvent(String& event); + + // хуки для системных событий + virtual void onRegEvent(IoTItem* item); //методы для графиков virtual void publishValue(); @@ -53,30 +60,38 @@ class IoTItem { virtual void setTodayDate(); protected: - String _subtype; - String _id; - unsigned long _interval; + bool _needSave = false; // признак необходимости сохранять и загружать значение элемента на flash + String _subtype = ""; + String _id = "errorId"; // если будет попытка создания Item без указания id, то элемент оставит это значение + long _interval = 0; + int _intFromNet = -2; // количество секунд доверия, пришедших из сети вместе с данными для текущего ИД + // -2 - данные не приходили, скорее всего, элемент локальный, доверие есть + // -1 - данные приходили и обратный отсчет дошел до нуля, значит доверия нет float _multiply; // умножаем на значение float _plus; // увеличиваем на значение - int _map1; - int _map2; - int _map3; - int _map4; - int _round; // 1, 10, 100, 1000, 10000 + int _map1 = 0; + int _map2 = 0; + int _map3 = 0; + int _map4 = 0; + int _round = 1; // 1, 10, 100, 1000, 10000 bool _global = false; // характеристика айтема, что ему нужно слать и принимать события из внешнего мира }; -IoTItem* findIoTItem(String name); // поиск экземпляра элемента модуля по имени -String getItemValue(String name); // поиск плюс получение значения -bool isItemExist(String name); // существует ли айтем -StaticJsonDocument* getLocalItemsAsJSON(); // сбор всех локальных занчений Items +IoTItem* findIoTItem(const String& name); // поиск экземпляра элемента модуля по имени +String getItemValue(const String& name); // поиск плюс получение значения +bool isItemExist(const String& name); // существует ли айтем +StaticJsonDocument* getLocalItemsAsJSON(); // сбор всех локальных значений Items -class externalVariable : IoTItem { // объект, создаваемый при получении информации о событии на другом контроллере для хранения информации о событии указанное время +IoTItem* createItemFromNet(const String& itemId, const String& value, int interval); +IoTItem* createItemFromNet(const String& msgFromNet); +void analyzeMsgFromNet(const String& msg, String altId = ""); - public: - externalVariable(String parameters); - ~externalVariable(); - void doByInterval(); // для данного класса doByInterval+int выполняет роль счетчика обратного отсчета до уничтожения -}; \ No newline at end of file +// class externalVariable : IoTItem { // объект, создаваемый при получении информации о событии на другом контроллере для хранения информации о событии указанное время + +// public: +// externalVariable(const String& parameters); +// ~externalVariable(); +// void doByInterval(); // для данного класса doByInterval+int выполняет роль счетчика обратного отсчета до уничтожения +// }; \ No newline at end of file diff --git a/include/classes/IoTScenario.h b/include/classes/IoTScenario.h index 83a47531..c1a6cf83 100644 --- a/include/classes/IoTScenario.h +++ b/include/classes/IoTScenario.h @@ -7,8 +7,8 @@ class ExprAST { public: virtual ~ExprAST(); virtual IoTValue *exec(); - virtual int setValue(IoTValue *val); // ret 0 - установка значения не поддерживается наследником - virtual bool hasEventIdName(String eventIdName); + virtual int setValue(IoTValue *val, bool generateEvent); // ret 0 - установка значения не поддерживается наследником + virtual bool hasEventIdName(const String& eventIdName); }; class IoTScenario { @@ -93,7 +93,7 @@ class IoTScenario { public: void loadScenario(String fileName); - void exec(String eventIdName); + void exec(const String& eventIdName); IoTScenario(); ~IoTScenario(); diff --git a/include/utils/JsonUtils.h b/include/utils/JsonUtils.h index 3a6912e9..a9810dc2 100644 --- a/include/utils/JsonUtils.h +++ b/include/utils/JsonUtils.h @@ -10,20 +10,20 @@ extern String jsonWriteInt(String& json, String name, int value, bool e = true); extern String jsonWriteFloat(String& json, String name, float value, bool e = true); extern String jsonWriteBool(String& json, String name, boolean value, bool e = true); -extern bool jsonRead(String& json, String key, unsigned long& value, bool e = true); -extern bool jsonRead(String& json, String key, float& value, bool e = true); -extern bool jsonRead(String& json, String key, String& value, bool e = true); -extern bool jsonRead(String& json, String key, bool& value, bool e = true); -extern bool jsonRead(String& json, String key, int& value, bool e = true); +extern bool jsonRead(const String& json, String key, long& value, bool e = true); +extern bool jsonRead(const String& json, String key, float& value, bool e = true); +extern bool jsonRead(const String& json, String key, String& value, bool e = true); +extern bool jsonRead(const String& json, String key, bool& value, bool e = true); +extern bool jsonRead(const String& json, String key, int& value, bool e = true); -extern String jsonReadStr(String& json, String name, bool e = true); -extern int jsonReadInt(String& json, String name, bool e = true); -extern boolean jsonReadBool(String& json, String name, bool e = true); +extern String jsonReadStr(const String& json, String name, bool e = true); +extern int jsonReadInt(const String& json, String name, bool e = true); +extern boolean jsonReadBool(const String& json, String name, bool e = true); -extern bool jsonWriteStr_(String& json, String name, String value, bool e = true); -extern bool jsonWriteBool_(String& json, String name, bool value, bool e = true); -extern bool jsonWriteInt_(String& json, String name, int value, bool e = true); -extern bool jsonWriteFloat_(String& json, String name, float value, bool e = true); +extern bool jsonWriteStr_(String& json, const String& name, const String& value, bool e = true); +extern bool jsonWriteBool_(String& json, const String& name, bool value, bool e = true); +extern bool jsonWriteInt_(String& json, const String& name, int value, bool e = true); +extern bool jsonWriteFloat_(String& json, const String& name, float value, bool e = true); void writeUint8tValueToJsonString(uint8_t* payload, size_t length, size_t headerLenth, String& json); extern bool jsonMergeObjects(String& json1, String& json2, bool e = true); extern void jsonMergeDocs(JsonObject dest, JsonObjectConst src); diff --git a/include/utils/SerialPrint.h b/include/utils/SerialPrint.h index 62efd173..73e6c787 100644 --- a/include/utils/SerialPrint.h +++ b/include/utils/SerialPrint.h @@ -1,5 +1,6 @@ #pragma once #include "Global.h" #include "utils/TimeUtils.h" +#include "classes/IoTItem.h" -void SerialPrint(String errorLevel, String module, String msg); \ No newline at end of file +void SerialPrint(const String& errorLevel, const String& module, const String& msg, const String& itemId = ""); \ No newline at end of file diff --git a/include/utils/StringUtils.h b/include/utils/StringUtils.h index 5a1edc41..816d8353 100644 --- a/include/utils/StringUtils.h +++ b/include/utils/StringUtils.h @@ -8,31 +8,31 @@ void hex2string(byte array[], unsigned int len, char buffer[]); int string2hex(const char* str, unsigned char* bytes); -uint8_t hexStringToUint8(String hex); +uint8_t hexStringToUint8(const String& hex); -uint16_t hexStringToUint16(String hex); +uint16_t hexStringToUint16(const String& hex); -String selectToMarkerLast(String str, String found); +String selectToMarkerLast(String str, const String& found); -String selectToMarker(String str, String found); +String selectToMarker(String str, const String& found); String extractInner(String str); -String deleteAfterDelimiter(String str, String found); +String deleteAfterDelimiter(String str, const String& found); -String deleteBeforeDelimiter(String str, String found); +String deleteBeforeDelimiter(String str, const String& found); -String deleteBeforeDelimiterTo(String str, String found); +String deleteBeforeDelimiterTo(String str, const String& found); -String deleteToMarkerLast(String str, String found); +String deleteToMarkerLast(String str, const String& found); -String selectFromMarkerToMarker(String str, String found, int number); +String selectFromMarkerToMarker(String str, const String& found, int number); size_t itemsCount2(String str, const String& separator); -char* stringToChar(String& str); +char* stringToChar(const String& str); -size_t itemsCount(String& str, const char* delim); +//size_t itemsCount(String& str, const char* delim); boolean isDigitStr(const String& str); @@ -40,4 +40,8 @@ boolean isDigitDotCommaStr(const String& str); String prettyBytes(size_t size); -String uint64ToString(uint64_t input); +String uint64ToString(uint64_t input, uint8_t base = 10); + +void cleanString(String& str); + +unsigned char ChartoHex(char ch); diff --git a/lib/WebSockets/.clang-format b/lib/WebSockets/.clang-format new file mode 100644 index 00000000..e72c54b1 --- /dev/null +++ b/lib/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/WebSockets/.github/workflows/main.yml b/lib/WebSockets/.github/workflows/main.yml new file mode 100644 index 00000000..d7bea656 --- /dev/null +++ b/lib/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/WebSockets/.gitignore b/lib/WebSockets/.gitignore new file mode 100644 index 00000000..267af0de --- /dev/null +++ b/lib/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/WebSockets/.piopm b/lib/WebSockets/.piopm new file mode 100644 index 00000000..3871c04b --- /dev/null +++ b/lib/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/WebSockets/.travis.yml b/lib/WebSockets/.travis.yml new file mode 100644 index 00000000..6cdf5db4 --- /dev/null +++ b/lib/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/WebSockets/LICENSE b/lib/WebSockets/LICENSE new file mode 100644 index 00000000..f166cc57 --- /dev/null +++ b/lib/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/WebSockets/README.md b/lib/WebSockets/README.md new file mode 100644 index 00000000..ae2497f0 --- /dev/null +++ b/lib/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/WebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf b/lib/WebSockets/examples/Nginx/esp8266.ssl.reverse.proxy.conf new file mode 100644 index 00000000..ec5aa89f --- /dev/null +++ b/lib/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/WebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino b/lib/WebSockets/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino new file mode 100644 index 00000000..9d49d149 --- /dev/null +++ b/lib/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/WebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino b/lib/WebSockets/examples/esp32/WebSocketClient/WebSocketClient.ino new file mode 100644 index 00000000..5e5ead46 --- /dev/null +++ b/lib/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/WebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino b/lib/WebSockets/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino new file mode 100644 index 00000000..9d722427 --- /dev/null +++ b/lib/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/WebSockets/examples/esp32/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino b/lib/WebSockets/examples/esp32/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino new file mode 100644 index 00000000..af3572f9 --- /dev/null +++ b/lib/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/WebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino b/lib/WebSockets/examples/esp32/WebSocketServer/WebSocketServer.ino new file mode 100644 index 00000000..3e0d4f5b --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino b/lib/WebSockets/examples/esp8266/WebSocketClient/WebSocketClient.ino new file mode 100644 index 00000000..5ee489cd --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientOTA/README.md b/lib/WebSockets/examples/esp8266/WebSocketClientOTA/README.md new file mode 100644 index 00000000..496eef25 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientOTA/WebSocketClientOTA.ino b/lib/WebSockets/examples/esp8266/WebSocketClientOTA/WebSocketClientOTA.ino new file mode 100644 index 00000000..2c87c251 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/main.py b/lib/WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/main.py new file mode 100644 index 00000000..7e7fba11 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/requirements.txt b/lib/WebSockets/examples/esp8266/WebSocketClientOTA/python_ota_server/requirements.txt new file mode 100644 index 00000000..4fc2553f --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino b/lib/WebSockets/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino new file mode 100644 index 00000000..d45060e9 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientSSLWithCA/WebSocketClientSSLWithCA.ino b/lib/WebSockets/examples/esp8266/WebSocketClientSSLWithCA/WebSocketClientSSLWithCA.ino new file mode 100644 index 00000000..214f5e61 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino b/lib/WebSockets/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino new file mode 100644 index 00000000..5ceacea4 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino b/lib/WebSockets/examples/esp8266/WebSocketClientSocketIOack/WebSocketClientSocketIOack.ino new file mode 100644 index 00000000..3e4f87e1 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino b/lib/WebSockets/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino new file mode 100644 index 00000000..a0eb011f --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino b/lib/WebSockets/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino new file mode 100644 index 00000000..cb0c45be --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino b/lib/WebSockets/examples/esp8266/WebSocketServer/WebSocketServer.ino new file mode 100644 index 00000000..1ac3002d --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino b/lib/WebSockets/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino new file mode 100644 index 00000000..5fed1a95 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino b/lib/WebSockets/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino new file mode 100644 index 00000000..84c9775d --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServerHooked/WebSocketServerHooked.ino b/lib/WebSockets/examples/esp8266/WebSocketServerHooked/WebSocketServerHooked.ino new file mode 100644 index 00000000..e762626b --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServerHooked/emu b/lib/WebSockets/examples/esp8266/WebSocketServerHooked/emu new file mode 100644 index 00000000..867a8cd7 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServerHooked/ws-testclient.py b/lib/WebSockets/examples/esp8266/WebSocketServerHooked/ws-testclient.py new file mode 100644 index 00000000..546c7ff2 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino b/lib/WebSockets/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino new file mode 100644 index 00000000..8bc646c4 --- /dev/null +++ b/lib/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/WebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino b/lib/WebSockets/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino new file mode 100644 index 00000000..8f32e753 --- /dev/null +++ b/lib/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/WebSockets/examples/particle/ParticleWebSocketClient/application.cpp b/lib/WebSockets/examples/particle/ParticleWebSocketClient/application.cpp new file mode 100644 index 00000000..461228f3 --- /dev/null +++ b/lib/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/WebSockets/library.json b/lib/WebSockets/library.json new file mode 100644 index 00000000..86758316 --- /dev/null +++ b/lib/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": "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/WebSockets/library.properties b/lib/WebSockets/library.properties new file mode 100644 index 00000000..aab19e85 --- /dev/null +++ b/lib/WebSockets/library.properties @@ -0,0 +1,9 @@ +name=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/WebSockets/src/SocketIOclient.cpp b/lib/WebSockets/src/SocketIOclient.cpp new file mode 100644 index 00000000..bf611953 --- /dev/null +++ b/lib/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/WebSockets/src/SocketIOclient.h b/lib/WebSockets/src/SocketIOclient.h new file mode 100644 index 00000000..2eccea3d --- /dev/null +++ b/lib/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/WebSockets/src/WebSockets.cpp b/lib/WebSockets/src/WebSockets.cpp new file mode 100644 index 00000000..6ab766fd --- /dev/null +++ b/lib/WebSockets/src/WebSockets.cpp @@ -0,0 +1,761 @@ +/** + * @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" { +#ifdef CORE_HAS_LIBB64 +#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 + +extern "C" { +#include "libsha1/libsha1.h" +} + +#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); + } + } + } + } +} diff --git a/lib/WebSockets/src/WebSockets.h b/lib/WebSockets/src/WebSockets.h new file mode 100644 index 00000000..3b5cb010 --- /dev/null +++ b/lib/WebSockets/src/WebSockets.h @@ -0,0 +1,367 @@ +/** + * @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 +#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) + +#if !defined(ESP8266) && !defined(ESP31B) +#error "network type ESP8266 only possible on the ESP mcu!" +#endif + +#ifdef 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 +#else +#include +#include +#define WEBSOCKETS_NETWORK_CLASS EthernetClient +#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer +#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 + +#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/WebSockets/src/WebSockets4WebServer.h b/lib/WebSockets/src/WebSockets4WebServer.h new file mode 100644 index 00000000..a542f1ea --- /dev/null +++ b/lib/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/WebSockets/src/WebSocketsClient.cpp b/lib/WebSockets/src/WebSocketsClient.cpp new file mode 100644 index 00000000..91f08d75 --- /dev/null +++ b/lib/WebSockets/src/WebSocketsClient.cpp @@ -0,0 +1,975 @@ +/** + * @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()); +} + +void WebSocketsClient::begin(IPAddress host, uint16_t port, const char * url, const char * protocol) { + return begin(host.toString().c_str(), port, url, protocol); +} + +#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(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 + + 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(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + _client.tcp->setNoDelay(true); +#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/WebSockets/src/WebSocketsClient.h b/lib/WebSockets/src/WebSocketsClient.h new file mode 100644 index 00000000..33028b98 --- /dev/null +++ b/lib/WebSockets/src/WebSocketsClient.h @@ -0,0 +1,169 @@ +/** + * @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"); + void begin(IPAddress host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); + +#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/WebSockets/src/WebSocketsServer.cpp b/lib/WebSockets/src/WebSocketsServer.cpp new file mode 100644 index 00000000..39d8d3e2 --- /dev/null +++ b/lib/WebSockets/src/WebSocketsServer.cpp @@ -0,0 +1,961 @@ +/** + * @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" + +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(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) + client->isSSL = false; + client->tcp->setNoDelay(true); +#endif +#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(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 + + 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/WebSockets/src/WebSocketsServer.h b/lib/WebSockets/src/WebSocketsServer.h new file mode 100644 index 00000000..a8472a5b --- /dev/null +++ b/lib/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/WebSockets/src/WebSocketsVersion.h b/lib/WebSockets/src/WebSocketsVersion.h new file mode 100644 index 00000000..bf2526c1 --- /dev/null +++ b/lib/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/WebSockets/src/libb64/AUTHORS b/lib/WebSockets/src/libb64/AUTHORS new file mode 100644 index 00000000..af687375 --- /dev/null +++ b/lib/WebSockets/src/libb64/AUTHORS @@ -0,0 +1,7 @@ +libb64: Base64 Encoding/Decoding Routines +====================================== + +Authors: +------- + +Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com diff --git a/lib/WebSockets/src/libb64/LICENSE b/lib/WebSockets/src/libb64/LICENSE new file mode 100644 index 00000000..a6b56069 --- /dev/null +++ b/lib/WebSockets/src/libb64/LICENSE @@ -0,0 +1,29 @@ +Copyright-Only Dedication (based on United States law) +or Public Domain Certification + +The person or persons who have associated work with this document (the +"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of +his knowledge, the work of authorship identified is in the public domain of the +country from which the work is published, or (b) hereby dedicates whatever +copyright the dedicators holds in the work of authorship identified below (the +"Work") to the public domain. A certifier, moreover, dedicates any copyright +interest he may have in the associated work, and for these purposes, is +described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this +work. Certifier recognizes that his good faith efforts may not shield him from +liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to +the detriment of the Dedicator's heirs and successors. Dedicator intends this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights under copyright law, whether vested or contingent, in the +Work. Dedicator understands that such relinquishment of all rights includes +the relinquishment of all rights to enforce (by lawsuit or otherwise) those +copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be +freely reproduced, distributed, transmitted, used, modified, built upon, or +otherwise exploited by anyone for any purpose, commercial or non-commercial, +and in any way, including by methods that have not yet been invented or +conceived. \ No newline at end of file diff --git a/lib/WebSockets/src/libb64/cdecode.c b/lib/WebSockets/src/libb64/cdecode.c new file mode 100644 index 00000000..e135da24 --- /dev/null +++ b/lib/WebSockets/src/libb64/cdecode.c @@ -0,0 +1,98 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#if defined(ESP32) +#define CORE_HAS_LIBB64 +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cdecode_inc.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +#endif diff --git a/lib/WebSockets/src/libb64/cdecode_inc.h b/lib/WebSockets/src/libb64/cdecode_inc.h new file mode 100644 index 00000000..d0d7f489 --- /dev/null +++ b/lib/WebSockets/src/libb64/cdecode_inc.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/lib/WebSockets/src/libb64/cencode.c b/lib/WebSockets/src/libb64/cencode.c new file mode 100644 index 00000000..afe1463c --- /dev/null +++ b/lib/WebSockets/src/libb64/cencode.c @@ -0,0 +1,119 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#if defined(ESP32) +#define CORE_HAS_LIBB64 +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cencode_inc.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = 0x00; + + return codechar - code_out; +} + +#endif diff --git a/lib/WebSockets/src/libb64/cencode_inc.h b/lib/WebSockets/src/libb64/cencode_inc.h new file mode 100644 index 00000000..c1e3464a --- /dev/null +++ b/lib/WebSockets/src/libb64/cencode_inc.h @@ -0,0 +1,31 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ diff --git a/lib/WebSockets/src/libsha1/libsha1.c b/lib/WebSockets/src/libsha1/libsha1.c new file mode 100644 index 00000000..48f4df5a --- /dev/null +++ b/lib/WebSockets/src/libsha1/libsha1.c @@ -0,0 +1,202 @@ +/* from valgrind tests */ + +/* ================ sha1.c ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#if !defined(ESP8266) && !defined(ESP32) + +#define SHA1HANDSOFF + +#include +#include +#include + +#include "libsha1.h" + + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) +{ + uint32_t a, b, c, d, e; + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16* block = (const CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) +{ + uint32_t i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ + unsigned i; + unsigned char finalcount[8]; + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t; + } +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} +/* ================ end of sha1.c ================ */ + + +#endif diff --git a/lib/WebSockets/src/libsha1/libsha1.h b/lib/WebSockets/src/libsha1/libsha1.h new file mode 100644 index 00000000..ee3718e1 --- /dev/null +++ b/lib/WebSockets/src/libsha1/libsha1.h @@ -0,0 +1,21 @@ +/* ================ sha1.h ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain +*/ + +#if !defined(ESP8266) && !defined(ESP32) + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); + +#endif diff --git a/lib/WebSockets/travis/common.sh b/lib/WebSockets/travis/common.sh new file mode 100644 index 00000000..c1c23285 --- /dev/null +++ b/lib/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/WebSockets/travis/version.py b/lib/WebSockets/travis/version.py new file mode 100644 index 00000000..71454abb --- /dev/null +++ b/lib/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/myProfile.json b/myProfile.json index 4e2a5610..132010d9 100644 --- a/myProfile.json +++ b/myProfile.json @@ -1,6 +1,5 @@ { "iotmSettings": { - "settings_": "", "name": "IoTmanagerVer4", "apssid": "IoTmanager", "appass": "", @@ -20,16 +19,20 @@ "mqttin": 0, "pinSCL": 0, "pinSDA": 0, - "i2cFreq": 100000 + "i2cFreq": 100000, + "wg": "group1" }, "projectProp": { "platformio": { - "default_envs": "esp8266_4mb", - "data_dir": "data_svelte" + "default_envs": "esp8266_4mb" } }, "modules": { "Виртуальные элементы": [ + { + "path": "src/modules/virtual/Cron", + "active": true + }, { "path": "src/modules/virtual/Loging", "active": true @@ -68,6 +71,10 @@ "path": "src/modules/sensors/AnalogAdc", "active": true }, + { + "path": "src/modules/sensors/Ble", + "active": false + }, { "path": "src/modules/sensors/Bme280", "active": true @@ -90,7 +97,7 @@ }, { "path": "src/modules/sensors/FreqMeter", - "active": true + "active": false }, { "path": "src/modules/sensors/GY21", @@ -100,6 +107,10 @@ "path": "src/modules/sensors/Hdc1080", "active": true }, + { + "path": "src/modules/sensors/Ina219", + "active": false + }, { "path": "src/modules/sensors/IoTWiegand", "active": false @@ -166,6 +177,10 @@ "path": "src/modules/exec/Mp3", "active": true }, + { + "path": "src/modules/exec/Multitouch", + "active": true + }, { "path": "src/modules/exec/Pcf8574", "active": true @@ -199,6 +214,10 @@ { "path": "src/modules/display/Lcd2004", "active": true + }, + { + "path": "src/modules/display/Ws2812b", + "active": true } ] } diff --git a/platformio.ini b/platformio.ini index b38209a2..3a5c9816 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,3 +1,45 @@ +[env:esp8266_1mb_ota] +lib_deps = + ${common_env_data.lib_deps_external} + ${env:esp8266_1mb_ota_fromitems.lib_deps} + ESPAsyncUDP +build_flags = -Desp8266_1mb_ota="esp8266_1mb_ota" +framework = arduino +board = nodemcuv2 +board_build.ldscript = eagle.flash.1m64.ld +platform = espressif8266 @4.0.1 +monitor_filters = esp8266_exception_decoder +upload_speed = 921600 +monitor_speed = 115200 +board_build.filesystem = littlefs +build_src_filter = + +<*.cpp> + + + + + + + ${env:esp8266_1mb_ota_fromitems.build_src_filter} + +[env:esp8266_1mb] +lib_deps = + ${common_env_data.lib_deps_external} + ${env:esp8266_1mb_fromitems.lib_deps} + ESPAsyncUDP +build_flags = -Desp8266_1mb="esp8266_1mb" +framework = arduino +board = nodemcuv2 +board_build.ldscript = eagle.flash.1m256.ld +platform = espressif8266 @4.0.1 +monitor_filters = esp8266_exception_decoder +upload_speed = 921600 +monitor_speed = 115200 +board_build.filesystem = littlefs +build_src_filter = + +<*.cpp> + + + + + + + ${env:esp8266_1mb_fromitems.build_src_filter} + [env:esp8266_4mb] lib_deps = ${common_env_data.lib_deps_external} @@ -9,7 +51,7 @@ board = nodemcuv2 board_build.ldscript = eagle.flash.4m1m.ld platform = espressif8266 @4.0.1 monitor_filters = esp8266_exception_decoder -upload_speed = 115200 +upload_speed = 921600 monitor_speed = 115200 board_build.filesystem = littlefs build_src_filter = @@ -29,7 +71,7 @@ framework = arduino board = esp32dev platform = espressif32 @5.1.1 monitor_filters = esp32_exception_decoder -upload_speed = 115200 +upload_speed = 921600 monitor_speed = 115200 debug_tool = esp-prog board_build.filesystem = littlefs @@ -45,12 +87,56 @@ default_envs = esp8266_4mb data_dir = data_svelte [common_env_data] -upload_port = COM4 lib_deps_external = bblanchon/ArduinoJson @6.18.0 - Links2004/WebSockets knolleary/PubSubClient +[env:esp8266_1mb_ota_fromitems] +lib_deps = + milesburton/DallasTemperature@^3.9.1 + rc-switch @ ^2.6.4 + 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 = + + + + + + + + + + + + + + + + + + + + + + + + + + + +[env:esp8266_1mb_fromitems] +lib_deps = + milesburton/DallasTemperature@^3.9.1 + rc-switch @ ^2.6.4 + 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 = + + + + + + + + + + + + + + + + + + + + + + + + + + + [env:esp8266_4mb_fromitems] lib_deps = https://github.com/enjoyneering/AHTxx.git @@ -58,11 +144,9 @@ lib_deps = adafruit/Adafruit BMP280 Library beegee-tokyo/DHT sensor library for ESPx milesburton/DallasTemperature@^3.9.1 - kosme/arduinoFFT@^1.5.6 https://github.com/JonasGMorsch/GY-21.git ClosedCube HDC1080 adafruit/MAX6675 library - https://github.com/mandulaj/PZEM-004T-v30 rc-switch @ ^2.6.4 robtillaart/SHT2x@^0.1.1 WEMOS SHT3x@1.0.0 @@ -72,6 +156,63 @@ lib_deps = dfrobot/DFRobotDFPlayerMini @ ^1.0.5 adafruit/Adafruit BusIO @ ^1.13.2 marcoschwartz/LiquidCrystal_I2C@^1.1.4 + adafruit/Adafruit NeoPixel@^1.10.6 +build_src_filter = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[env:esp32_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@^3.9.1 + https://github.com/JonasGMorsch/GY-21.git + ClosedCube HDC1080 + adafruit/MAX6675 library + rc-switch @ ^2.6.4 + robtillaart/SHT2x@^0.1.1 + WEMOS SHT3x@1.0.0 + plerup/espsoftwareserial + https://github.com/RoboticsBrno/ServoESP32 + 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 + marcoschwartz/LiquidCrystal_I2C@^1.1.4 + adafruit/Adafruit NeoPixel@^1.10.6 build_src_filter = + + @@ -85,7 +226,6 @@ build_src_filter = + + + - + + + + @@ -101,58 +241,8 @@ build_src_filter = + + + - + + + + + - -[env:esp32_4mb_fromitems] -lib_deps = - Adafruit AHTX0 - adafruit/Adafruit BME280 Library - adafruit/Adafruit BMP280 Library - beegee-tokyo/DHT sensor library for ESPx - milesburton/DallasTemperature@^3.9.1 - https://github.com/JonasGMorsch/GY-21.git - ClosedCube HDC1080 - adafruit/MAX6675 library - mandulaj/PZEM-004T-v30 - rc-switch @ ^2.6.4 - robtillaart/SHT2x@^0.1.1 - WEMOS SHT3x@1.0.0 - plerup/espsoftwareserial - https://github.com/RoboticsBrno/ServoESP32 - adafruit/Adafruit MCP23017 Arduino Library@^2.1.0 - adafruit/Adafruit BusIO @ ^1.13.2 - dfrobot/DFRobotDFPlayerMini @ ^1.0.5 - marcoschwartz/LiquidCrystal_I2C@^1.1.4 -build_src_filter = - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + diff --git a/src/Buffers.cpp b/src/Buffers.cpp index d8338cb2..79d31500 100644 --- a/src/Buffers.cpp +++ b/src/Buffers.cpp @@ -1,35 +1,35 @@ -#include "Buffers.h" +//#include "Buffers.h" //генеирует событие -void eventGen2(String eventName, String eventValue) { - if (!jsonReadBool(settingsFlashJson, "scen")) { - return; - } - String event = eventName + " " + eventValue + ","; - eventBuf += event; +// void eventGen2(String eventName, String eventValue) { +// if (!jsonReadBool(settingsFlashJson, "scen")) { +// return; +// } +// String event = eventName + " " + eventValue + ","; +// eventBuf += event; - SerialPrint("i", "Event add", eventName + " " + eventValue); +// SerialPrint("i", "Event add", eventName + " " + eventValue); - if (jsonReadBool(settingsFlashJson, "MqttOut")) { - if (eventName != "timenow") { - publishEvent(eventName, eventValue); - } - } -} +// if (jsonReadBool(settingsFlashJson, "MqttOut")) { +// if (eventName != "timenow") { +// publishEvent(eventName, eventValue); +// } +// } +// } -void spaceCmdExecute(String& cmdStr) { - cmdStr += "\r\n"; - cmdStr.replace("\r\n", "\n"); - cmdStr.replace("\r", "\n"); - while (cmdStr.length()) { - String buf = selectToMarker(cmdStr, "\n"); - if (buf != "") { - sCmd.readStr(buf); - SerialPrint("i", F("Order done W"), buf); - } - cmdStr = deleteBeforeDelimiter(cmdStr, "\n"); - } -} +// void spaceCmdExecute(String& cmdStr) { +// cmdStr += "\r\n"; +// cmdStr.replace("\r\n", "\n"); +// cmdStr.replace("\r", "\n"); +// while (cmdStr.length()) { +// String buf = selectToMarker(cmdStr, "\n"); +// if (buf != "") { +// sCmd.readStr(buf); +// SerialPrint("i", F("Order done W"), buf); +// } +// cmdStr = deleteBeforeDelimiter(cmdStr, "\n"); +// } +// } // String getValueJson(String& key) { // String live = jsonReadStr(paramsHeapJson, key); diff --git a/src/DeviceList.cpp b/src/DeviceList.cpp index c9998917..9e5d99c2 100644 --- a/src/DeviceList.cpp +++ b/src/DeviceList.cpp @@ -2,7 +2,8 @@ const String getThisDevice() { String thisDevice = "{}"; - jsonWriteStr_(thisDevice, F("devicelist_"), ""); //метка для парсинга + jsonWriteStr_(thisDevice, F("devicelist_"), ""); //метка для парсинга + jsonWriteStr_(thisDevice, F("wg"), jsonReadStr(settingsFlashJson, F("wg"))); //рабочая группа jsonWriteStr_(thisDevice, F("ip"), jsonReadStr(settingsFlashJson, F("ip"))); jsonWriteStr_(thisDevice, F("id"), jsonReadStr(settingsFlashJson, F("id"))); jsonWriteStr_(thisDevice, F("name"), jsonReadStr(settingsFlashJson, F("name"))); @@ -52,7 +53,7 @@ void asyncUdpInit() { }); } - //будем отправлять каждые 30 секунд презентацию данного устройства + //будем отправлять каждые 60 секунд презентацию данного устройства ts.add( UDP, 60000, [&](void*) { // UDPP if (isNetworkActive()) { @@ -68,7 +69,9 @@ void asyncUdpInit() { } bool udpPacketValidation(String& data) { - if (data.indexOf("devicelist") != -1) { + // SerialPrint("i", F("UDP"), data); + String workgroup = jsonReadStr(settingsFlashJson, "wg"); + if (workgroup != "" && data.indexOf(workgroup) != -1) { return true; } else { return false; diff --git a/src/ESPConfiguration.cpp b/src/ESPConfiguration.cpp index 0a70e979..1ab3a874 100644 --- a/src/ESPConfiguration.cpp +++ b/src/ESPConfiguration.cpp @@ -46,4 +46,6 @@ void clearConfigure() { if (*it) delete *it; } IoTItems.clear(); + + valuesFlashJson.clear(); } \ No newline at end of file diff --git a/src/EspFileSystem.cpp b/src/EspFileSystem.cpp index 91dd4bf1..5f8619e8 100644 --- a/src/EspFileSystem.cpp +++ b/src/EspFileSystem.cpp @@ -14,13 +14,17 @@ void globalVarsSync() { settingsFlashJson = readFile(F("settings.json"), 4096); settingsFlashJson.replace("\r\n", ""); + valuesFlashJson = readFile(F("values.json"), 4096); + valuesFlashJson.replace("\r\n", ""); + + mqttPrefix = jsonReadStr(settingsFlashJson, F("mqttPrefix")); mqttRootDevice = mqttPrefix + "/" + chipId; jsonWriteStr_(settingsFlashJson, "root", mqttRootDevice); jsonWriteStr_(settingsFlashJson, "id", chipId); - jsonWriteStr_(errorsHeapJson, "errors_", ""); //метка для парсинга - jsonWriteStr_(ssidListHeapJson, "ssids_", ""); //метка для парсинга + // jsonWriteStr_(errorsHeapJson, "errors_", ""); //метка для парсинга удалить + // jsonWriteStr_(ssidListHeapJson, "ssids_", ""); //метка для парсинга удалить } String getParamsJson() { @@ -34,6 +38,10 @@ void syncSettingsFlashJson() { writeFile(F("settings.json"), settingsFlashJson); } +void syncValuesFlashJson() { + writeFile(F("values.json"), valuesFlashJson); +} + const String getChipId() { return String(ESP_getChipId()) + "-" + String(ESP_getFlashChipId()); } diff --git a/src/EventsAndOrders.cpp b/src/EventsAndOrders.cpp index bb6db4a9..2af00a66 100644 --- a/src/EventsAndOrders.cpp +++ b/src/EventsAndOrders.cpp @@ -27,10 +27,6 @@ void handleOrder() { String id = selectToMarker(order, " "); - //это модификатор для даты графика - // if (id.endsWith("-date")) { - //} - //здесь нужно перебрать все методы execute всех векторов и выполнить те id которых совпали с id события IoTItem* item = findIoTItem(id); if (item) { diff --git a/src/Global.cpp b/src/Global.cpp index 5aa28dea..309e379a 100644 --- a/src/Global.cpp +++ b/src/Global.cpp @@ -31,8 +31,10 @@ WebSocketsServer standWebSocket = WebSocketsServer(81); **********************************************************************************************************************/ IoTGpio IoTgpio(0); -String settingsFlashJson = "{}"; //переменная в которой хранятся все настройки, находится в оперативной памяти и синхронизированна с flash памятью -String errorsHeapJson = "{}"; //переменная в которой хранятся все ошибки, находится в оперативной памяти только +String settingsFlashJson = "{}"; // переменная в которой хранятся все настройки, находится в оперативной памяти и синхронизированна с flash памятью +String valuesFlashJson = "{}"; // переменная в которой хранятся все значения элементов, которые необходимо сохранить на flash. Находится в оперативной памяти и синхронизированна с flash памятью +String errorsHeapJson = "{}"; // переменная в которой хранятся все ошибки, находится в оперативной памяти только +bool needSaveValues = false; // признак необходимости сбросить значения элементов на flash // buf String orderBuf = ""; @@ -68,6 +70,8 @@ String mqttRootDevice = ""; unsigned long unixTime = 0; unsigned long unixTimeShort = 0; +//unsigned long loopPeriod; + bool isTimeSynch = false; Time_t _time_local; Time_t _time_utc; diff --git a/src/Main.cpp b/src/Main.cpp index dd750d81..94ae3e25 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -59,11 +59,12 @@ void setup() { mqttInit(); // настраиваем i2c шину - int pinSCL, pinSDA, i2cFreq; + int i2c, pinSCL, pinSDA, i2cFreq; jsonRead(settingsFlashJson, "pinSCL", pinSCL, false); jsonRead(settingsFlashJson, "pinSDA", pinSDA, false); jsonRead(settingsFlashJson, "i2cFreq", i2cFreq, false); - if (pinSCL && pinSDA && i2cFreq) { + jsonRead(settingsFlashJson, "i2c", i2c, false); + if (i2c != 0) { #ifdef esp32_4mb Wire.end(); Wire.begin(pinSDA, pinSCL, (uint32_t)i2cFreq); @@ -71,8 +72,8 @@ void setup() { Wire.begin(pinSDA, pinSCL); Wire.setClock(i2cFreq); #endif + SerialPrint("i", "i2c", F("i2c pins overriding done")); } - //настраиваем микроконтроллер configure("/config.json"); @@ -90,11 +91,28 @@ void setup() { iotScen.loadScenario("/scenario.txt"); // создаем событие завершения конфигурирования для возможности выполнения блока кода при загрузке - IoTItems.push_back((IoTItem *)new externalVariable("{\"id\":\"onStart\",\"val\":1,\"int\":60}")); - generateEvent("onStart", ""); + createItemFromNet("onStart", "1", -4); stInit(); + // настраиваем секундные обслуживания системы + ts.add( + TIMES, 1000, [&](void *) { + // сохраняем значения IoTItems в файл каждую секунду, если были изменения (установлены маркеры на сохранение) + if (needSaveValues) { + syncValuesFlashJson(); + needSaveValues = false; + } + + // проверяем все элементы на тухлость + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { + (*it)->checkIntFromNet(); + + // Serial.printf("[ITEM] size: %d, id: %s, int: %d, intnet: %d\n", sizeof(**it), (*it)->getID(), (*it)->getInterval(), (*it)->getIntFromNet()); + } + }, + nullptr, true); + // test Serial.println("-------test start--------"); Serial.println("--------test end---------"); @@ -117,42 +135,28 @@ void setup() { } void loop() { - // if(millis()%2000==0){ - // //watch->settimeUnix(time(&iotTimeNow)); - // Serial.println(watch->gettime("d-m-Y, H:i:s, M")); - // delay(1); - // } - - //обновление задач таскера - ts.update(); - -//отправка json -#ifdef QUEUE_FROM_STR - if (sendJsonFiles) sendJsonFiles->loop(); +#ifdef LOOP_DEBUG + unsigned long st = millis(); #endif + ts.update(); + #ifdef STANDARD_WEB_SERVER - //обработка web сервера 1 HTTP.handleClient(); #endif #ifdef STANDARD_WEB_SOCKETS - //обработка web сокетов standWebSocket.loop(); #endif - //обновление mqtt mqttLoop(); -#ifdef STANDARD_WEB_SERVER - //обработка web сервера 2 - // HTTP.handleClient(); -#endif - // передаем управление каждому элементу конфигурации для выполнения своих функций for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { (*it)->loop(); - if ((*it)->iAmDead) { + + // if ((*it)->iAmDead) { + if (!((*it)->iAmLocal) && (*it)->getIntFromNet() == -1) { delete *it; IoTItems.erase(it); break; @@ -160,33 +164,25 @@ void loop() { } handleOrder(); - handleEvent(); -#ifdef STANDARD_WEB_SERVER - //обработка web сервера 3 - // HTTP.handleClient(); -#endif - - // сохраняем значения IoTItems в файл каждую секунду, если были изменения (установлены маркеры на сохранение) - // currentMillis = millis(); - // if (currentMillis - prevMillis >= 1000) { - // prevMillis = millis(); - // volStrForSave = ""; - // for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { - // if ((*it)->needSave) { - // (*it)->needSave = false; - // volStrForSave = volStrForSave + (*it)->getID() + "=" + (*it)->getValue() + ";"; - // } - // } - // - // if (volStrForSave != "") { - // Serial.print("volStrForSave: "); - // Serial.println(volStrForSave.c_str()); - // } - //} + // #ifdef LOOP_DEBUG + // loopPeriod = millis() - st; + // if (loopPeriod > 2) Serial.println(loopPeriod); + // #endif } +//отправка json +//#ifdef QUEUE_FROM_STR +// if (sendJsonFiles) sendJsonFiles->loop(); +//#endif + +// if(millis()%2000==0){ +// //watch->settimeUnix(time(&iotTimeNow)); +// Serial.println(watch->gettime("d-m-Y, H:i:s, M")); +// delay(1); +// } + // File dir = FileFS.open("/", "r"); // String out; // printDirectory(dir, out); diff --git a/src/MqttClient.cpp b/src/MqttClient.cpp index 89a2ba7e..28d37249 100644 --- a/src/MqttClient.cpp +++ b/src/MqttClient.cpp @@ -160,34 +160,19 @@ void mqttCallback(char* topic, uint8_t* payload, size_t length) { //здесь мы получаем события с других устройств, которые потом проверяются в сценариях этого устройства else if (topicStr.indexOf("event") != -1) { //пока не работает сетевой обмен этот код будет закомментирован - - // if (!jsonReadBool(settingsFlashJson, "mqttin")) { - // return; - // } - // if (topicStr.indexOf(chipId) == -1) { - // String devId = selectFromMarkerToMarker(topicStr, "/", 2); - // String id = selectFromMarkerToMarker(topicStr, "/", 3); - // IoTItem* itemExist = findIoTItem(id); - // if (itemExist) { - // String valAsStr; - // if (jsonRead(payloadStr, F("val"), valAsStr, false)) { - // itemExist->setValue(valAsStr); - // unsigned long interval; - // jsonRead(payloadStr, F("int"), interval); - // itemExist->setInterval(interval); - // } - // } else { - // //добавим событие в базу - // // IoTItems.push_back((IoTItem*)new externalVariable(payloadStr)); - // } - // //запустим проверку его в сценариях - // generateEvent(id, payloadStr); - // SerialPrint("i", F("=>MQTT"), "Received event from other device: '" + devId + "' " + id + " " + payloadStr); - // } + if (!jsonReadBool(settingsFlashJson, "mqttin")) { + return; + } + if (topicStr.indexOf(chipId) == -1) { + String devId = selectFromMarkerToMarker(topicStr, "/", 2); + String id = selectFromMarkerToMarker(topicStr, "/", 3); + analyzeMsgFromNet(payloadStr, id); + //SerialPrint("i", F("=>MQTT"), "Received event from other device: '" + devId + "' " + id + " " + valAsStr); + } } - //здесь мы получаем прямые команды которые сразу выполнятся на этом устройстве - //необходимо для тех кто хочет управлять своим устройством из mqtt + // здесь мы получаем прямые команды которые сразу выполнятся на этом устройстве + // необходимо для тех кто хочет управлять своим устройством из mqtt else if (topicStr.indexOf("order") != -1) { if (!jsonReadBool(settingsFlashJson, "mqttin")) { return; @@ -278,7 +263,7 @@ void publishWidgets() { DeserializationError error = deserializeJson(doc, file); if (error) { SerialPrint("E", F("MQTT"), error.f_str()); - handleError("jse3", 1); + jsonWriteInt(errorsHeapJson, F("jse3"), 1); //Ошибка чтения json файла с виджетами при отправки в mqtt } JsonArray arr = doc.as(); for (JsonVariant value : arr) { @@ -325,14 +310,15 @@ void handleMqttStatus(bool send) { String stateStr = getStateStr(mqtt.state()); // Serial.println(stateStr); jsonWriteStr_(errorsHeapJson, F("mqtt"), stateStr); - if (!send) standWebSocket.broadcastTXT(errorsHeapJson); + + if (!send) sendStringToWs("errors", errorsHeapJson, -1); } void handleMqttStatus(bool send, int state) { String stateStr = getStateStr(state); // Serial.println(stateStr); jsonWriteStr_(errorsHeapJson, F("mqtt"), stateStr); - if (!send) standWebSocket.broadcastTXT(errorsHeapJson); + if (!send) sendStringToWs("errors", errorsHeapJson, -1); } // log-20384820.txt diff --git a/src/NTP.cpp b/src/NTP.cpp index 495ff9d2..e2f83178 100644 --- a/src/NTP.cpp +++ b/src/NTP.cpp @@ -13,9 +13,11 @@ void ntpInit() { if (unixTime < MIN_DATETIME) { isTimeSynch = false; // SerialPrint("E", "NTP", "Time not synched"); + jsonWriteInt(errorsHeapJson, F("tme1"), 1); synchTime(); return; } + jsonWriteInt(errorsHeapJson, F("tme1"), 0); breakEpochToTime(unixTime + jsonReadInt(settingsFlashJson, F("timezone")) * 60 * 60, _time_local); breakEpochToTime(unixTime, _time_utc); isTimeSynch = true; diff --git a/src/PeriodicTasks.cpp b/src/PeriodicTasks.cpp index 9885aafc..73e14dcc 100644 --- a/src/PeriodicTasks.cpp +++ b/src/PeriodicTasks.cpp @@ -31,27 +31,21 @@ void periodicTasksInit() { SerialPrint("i", "Task", "Periodic tasks init"); } -void handleError(String errorId, String errorValue) { - jsonWriteStr_(errorsHeapJson, errorId, errorValue); -} - -void handleError(String errorId, int errorValue) { - jsonWriteInt_(errorsHeapJson, errorId, errorValue); -} - void printGlobalVarSize() { size_t settingsFlashJsonSize = settingsFlashJson.length(); // SerialPrint(F("i"), F("settingsFlashJson"), String(settingsFlashJsonSize)); + size_t valuesFlashJsonSize = valuesFlashJson.length(); size_t errorsHeapJsonSize = errorsHeapJson.length(); // SerialPrint(F("i"), F("errorsHeapJson"), String(errorsHeapJsonSize)); size_t devListHeapJsonSize = devListHeapJson.length(); // SerialPrint(F("i"), F("devListHeapJson"), String(devListHeapJsonSize)); - SerialPrint(F("i"), F("Var summ sz"), String(settingsFlashJsonSize + errorsHeapJsonSize + devListHeapJsonSize)); + SerialPrint(F("i"), F("Var summ sz"), String(settingsFlashJsonSize + valuesFlashJsonSize + errorsHeapJsonSize + devListHeapJsonSize)); size_t halfBuffer = JSON_BUFFER_SIZE / 2; if (settingsFlashJsonSize > halfBuffer || + valuesFlashJsonSize > halfBuffer || errorsHeapJsonSize > halfBuffer || devListHeapJsonSize > halfBuffer) { SerialPrint(F("EE"), F("Json"), F("Insufficient buffer size!!!")); diff --git a/src/StandWebServer.cpp b/src/StandWebServer.cpp index 6a846158..aa609150 100644 --- a/src/StandWebServer.cpp +++ b/src/StandWebServer.cpp @@ -14,9 +14,9 @@ void standWebServerInit() { // HTTP.on("/devicelist.json", HTTP_GET, []() { // HTTP.send(200, "application/json", devListHeapJson); // }); - // HTTP.on("/settings.h.json", HTTP_GET, []() { - // HTTP.send(200, "application/json", settingsFlashJson); - // }); + HTTP.on("/settings.h.json", HTTP_GET, []() { + HTTP.send(200, "application/json", settingsFlashJson); + }); // HTTP.on("/settings.f.json", HTTP_GET, []() { // HTTP.send(200, "application/json", readFile(F("settings.json"), 20000)); // }); @@ -38,6 +38,22 @@ void standWebServerInit() { // HTTP.send(200, "text/plain", "ok"); // }); + + HTTP.on("/set", HTTP_GET, []() { + if (HTTP.hasArg(F("routerssid")) && WiFi.getMode() == WIFI_AP) { + jsonWriteStr(settingsFlashJson, F("routerssid"), HTTP.arg(F("routerssid"))); + syncSettingsFlashJson(); + HTTP.send(200, "text/plain", "ok"); + } + + if (HTTP.hasArg(F("routerpass")) && WiFi.getMode() == WIFI_AP) { + jsonWriteStr(settingsFlashJson, F("routerpass"), HTTP.arg(F("routerpass"))); + syncSettingsFlashJson(); + HTTP.send(200, "text/plain", "ok"); + } + + }); + // Добавляем функцию Update для перезаписи прошивки по WiFi при 1М(256K FileFS) и выше // httpUpdater.setup(&HTTP); @@ -98,6 +114,7 @@ void standWebServerInit() { } bool handleFileRead(String path) { + path = "/" + path; if (path.endsWith("/")) path += "index.html"; String contentType = getContentType(path); String pathWithGz = path + ".gz"; @@ -211,7 +228,7 @@ void handleFileList() { entry.close(); } output += "]"; - Serial.println(output); + //Serial.println(output); HTTP.send(200, "text/json", output); } diff --git a/src/UpgradeFirm.cpp b/src/UpgradeFirm.cpp index ec953ba7..53c6e15b 100644 --- a/src/UpgradeFirm.cpp +++ b/src/UpgradeFirm.cpp @@ -72,7 +72,7 @@ bool upgradeBuild() { handleUpdateStatus(true, PATH_ERROR); return ret; } -#ifdef esp8266_4mb +#if defined (esp8266_4mb) || defined (esp8266_1mb) || defined (esp8266_1mb_ota) ESPhttpUpdate.rebootOnUpdate(false); t_httpUpdate_return retBuild = ESPhttpUpdate.update(wifiClient, getBinPath("firmware.bin")); #endif @@ -132,5 +132,5 @@ void saveUserDataToFlash() { void handleUpdateStatus(bool send, int state) { jsonWriteInt_(errorsHeapJson, F("upd"), state); - if (!send) standWebSocket.broadcastTXT(errorsHeapJson); + if (!send) sendStringToWs("errors", errorsHeapJson, -1); } \ No newline at end of file diff --git a/src/WsServer.cpp b/src/WsServer.cpp index 79611e91..a3901634 100644 --- a/src/WsServer.cpp +++ b/src/WsServer.cpp @@ -56,19 +56,19 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) //отвечаем данными на запрос страницы if (headerStr == "/|") { - sendFileToWs("/layout.json", num, 1024); + sendFileToWsByFrames("/layout.json", "layout", "", num, WEB_SOCKETS_FRAME_SIZE); } //отвечаем на запрос параметров if (headerStr == "/params|") { String params = "{}"; - jsonWriteStr(params, "params_", ""); //метка для парсинга + // jsonWriteStr(params, "params_", ""); //метка для парсинга удалить for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { if ((*it)->getSubtype() != "Loging") { if ((*it)->iAmLocal) jsonWriteStr(params, (*it)->getID(), (*it)->getValue()); } } - standWebSocket.sendTXT(num, params); + sendStringToWs("params", params, num); } //отвечаем на запрос графиков @@ -93,16 +93,11 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) //отвечаем данными на запрос страницы if (headerStr == "/config|") { - sendFileToWs("/items.json", num, 1024); - sendFileToWs("/widgets.json", num, 1024); - sendFileToWs("/config.json", num, 1024); - sendFileToWs("/scenario.txt", num, 1024); - //шлется для того что бы получить топик устройства - standWebSocket.sendTXT(num, settingsFlashJson); - } - - //отправляем все графики в веб для экспорта - if (headerStr == "/expcharts|") { + sendFileToWsByFrames("/items.json", "itemsj", "", num, WEB_SOCKETS_FRAME_SIZE); + sendFileToWsByFrames("/widgets.json", "widget", "", num, WEB_SOCKETS_FRAME_SIZE); + sendFileToWsByFrames("/config.json", "config", "", num, WEB_SOCKETS_FRAME_SIZE); + sendFileToWsByFrames("/scenario.txt", "scenar", "", num, WEB_SOCKETS_FRAME_SIZE); + sendStringToWs("settin", settingsFlashJson, num); } //обработка кнопки сохранить @@ -118,8 +113,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) configure("/config.json"); iotScen.loadScenario("/scenario.txt"); // создаем событие завершения конфигурирования для возможности выполнения блока кода при загрузке - IoTItems.push_back((IoTItem*)new externalVariable("{\"id\":\"onStart\",\"val\":1,\"int\":60}")); - generateEvent("onStart", ""); + createItemFromNet("onStart", "1", -4); } //----------------------------------------------------------------------// @@ -128,9 +122,9 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) //отвечаем данными на запрос страницы if (headerStr == "/connection|") { - standWebSocket.sendTXT(num, settingsFlashJson); - standWebSocket.sendTXT(num, ssidListHeapJson); - standWebSocket.sendTXT(num, errorsHeapJson); + sendStringToWs("settin", settingsFlashJson, num); + sendStringToWs("ssidli", ssidListHeapJson, num); + sendStringToWs("errors", errorsHeapJson, num); // запуск асинхронного сканирования wifi сетей при переходе на страницу соединений // RouterFind(jsonReadStr(settingsFlashJson, F("routerssid"))); } @@ -139,23 +133,23 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) if (headerStr == "/sgnittes|") { writeUint8tToString(payload, length, headerLenth, settingsFlashJson); writeFileUint8tByFrames("settings.json", payload, length, headerLenth, 256); - standWebSocket.sendTXT(num, errorsHeapJson); + sendStringToWs("errors", errorsHeapJson, num); addThisDeviceToList(); } //обработка кнопки сохранить настройки mqtt if (headerStr == "/mqtt|") { - standWebSocket.sendTXT(num, settingsFlashJson); //отправляем в ответ новые полученные настройки - handleMqttStatus(false, 8); //меняем статус на неопределенный - mqttReconnect(); //начинаем переподключение - standWebSocket.sendTXT(num, errorsHeapJson); //отправляем что статус неопределен - standWebSocket.sendTXT(num, ssidListHeapJson); + sendStringToWs("settin", settingsFlashJson, num); //отправляем в ответ новые полученные настройки + handleMqttStatus(false, 8); //меняем статус на неопределенный + mqttReconnect(); //начинаем переподключение + sendStringToWs("errors", errorsHeapJson, num); //отправляем что статус неопределен + sendStringToWs("ssidli", ssidListHeapJson, num); } //запуск асинхронного сканирования wifi сетей при нажатии выпадающего списка if (headerStr == "/scan|") { RouterFind(jsonReadStr(settingsFlashJson, F("routerssid"))); - standWebSocket.sendTXT(num, ssidListHeapJson); + sendStringToWs("ssidli", ssidListHeapJson, num); } //----------------------------------------------------------------------// @@ -164,7 +158,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) //отвечаем данными на запрос страницы if (headerStr == "/list|") { - standWebSocket.sendTXT(num, devListHeapJson); + sendStringToWs("devlis", devListHeapJson, num); } //----------------------------------------------------------------------// @@ -173,18 +167,22 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) //отвечаем данными на запрос страницы if (headerStr == "/system|") { - standWebSocket.sendTXT(num, errorsHeapJson); - standWebSocket.sendTXT(num, settingsFlashJson); + sendStringToWs("errors", errorsHeapJson, num); + sendStringToWs("settin", settingsFlashJson, num); } //----------------------------------------------------------------------// // Страница веб интерфейса dev //----------------------------------------------------------------------// if (headerStr == "/dev|") { - standWebSocket.sendTXT(num, errorsHeapJson); - standWebSocket.sendTXT(num, settingsFlashJson); - sendFileToWs("/config.json", num, 1024); - sendFileToWs("/items.json", num, 1024); + sendStringToWs("errors", errorsHeapJson, num); + sendStringToWs("settin", settingsFlashJson, num); + sendFileToWsByFrames("/config.json", "config", "", num, WEB_SOCKETS_FRAME_SIZE); + sendFileToWsByFrames("/items.json", "itemsj", "", num, WEB_SOCKETS_FRAME_SIZE); + // sendFileToWsByFrames("/layout.json", "layout", "", num, WEB_SOCKETS_FRAME_SIZE); + } + + if (headerStr == "/test|") { } //----------------------------------------------------------------------// @@ -220,6 +218,11 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) generateOrder(key, value); SerialPrint("i", F("=>WS"), "Msg from svelte web, WS No: " + String(num) + ", msg: " + msg); } + + if (headerStr == "/tst|") { + standWebSocket.sendTXT(num, "/tstr|"); + } + } break; case WStype_BIN: { @@ -264,33 +267,18 @@ void publishStatusWs(const String& topic, const String& data) { String json = "{}"; jsonWriteStr(json, "status", data); jsonWriteStr(json, "topic", path); - standWebSocket.broadcastTXT(json); + sendStringToWs("status", json, -1); } -//публикация статус сообщений -// void publishChartWs2(int num, String& data) { -// bool ok = false; -// if (num == -1) { -// ok = standWebSocket.broadcastTXT(data); -// } else { -// ok = standWebSocket.sendTXT(num, data); -// } -// if (ok) { -// SerialPrint(F("i"), F("WS"), F("sent sucsess")); -// } else { -// SerialPrint(F("E"), F("WS"), F("sent error")); -// } -//} - void publishChartWs(int num, String& path) { - sendFileToWs(path, num, 1000); + // sendFileToWs(path, num, 1000); } //данные которые мы отправляем в сокеты переодически void periodicWsSend() { - standWebSocket.broadcastTXT(devListHeapJson); - standWebSocket.broadcastTXT(ssidListHeapJson); - standWebSocket.broadcastTXT(errorsHeapJson); + sendStringToWs("ssidli", ssidListHeapJson, -1); + sendStringToWs("errors", errorsHeapJson, -1); + sendStringToWs("devlis", devListHeapJson, -1); } #ifdef ESP32 @@ -309,144 +297,91 @@ void hexdump(const void* mem, uint32_t len, uint8_t cols = 16) { #endif #endif -//посылка данных из файла в бинарном виде -void sendFileToWs(String filename, int num, size_t frameSize) { - String st = "/st" + String(filename); - if (num == -1) { - standWebSocket.broadcastTXT(st); - } else { - standWebSocket.sendTXT(num, st); - } - - String path = filepath(filename); - auto file = FileFS.open(path, "r"); - if (!file) { - SerialPrint(F("E"), F("FS"), F("reed file error")); +void sendFileToWsByFrames(const String& filename, const String& header, const String& json, int client_id, size_t frameSize) { + if (header.length() != 6) { + SerialPrint("E", "FS", F("wrong header size")); return; } - size_t fileSize = file.size(); - SerialPrint(F("i"), F("FS"), "Send file '" + String(filename) + "', file size: " + String(fileSize)); - uint8_t payload[frameSize]; - int countRead = file.read(payload, sizeof(payload)); - while (countRead > 0) { - if (num == -1) { - standWebSocket.broadcastBIN(payload, countRead); - } else { - standWebSocket.sendBIN(num, payload, countRead); - } - countRead = file.read(payload, sizeof(payload)); - } - file.close(); - String end = "/end" + String(filename); - if (num == -1) { - standWebSocket.broadcastTXT(end); - } else { - standWebSocket.sendTXT(num, end); - } -} -//посылка данных из string -void sendStringToWs(const String& msg, uint8_t num, String name) { - String st = "/st" + String(name); - standWebSocket.sendTXT(num, st); - size_t size = msg.length(); - char dataArray[size]; - msg.toCharArray(dataArray, size); - standWebSocket.sendBIN(num, (uint8_t*)dataArray, size); - String end = "/end" + String(name); - standWebSocket.sendTXT(num, end); -} - -//особая функция отправки графиков в веб -void publishChartToWs(String filename, int num, size_t frameSize, int maxCount, String id) { - String json; - jsonWriteStr(json, "topic", mqttRootDevice + "/" + id); - jsonWriteInt(json, "maxCount", maxCount); - - String st = "/st/chart.json|" + json; - if (num == -1) { - standWebSocket.broadcastTXT(st); - } else { - standWebSocket.sendTXT(num, st); - } - String path = filepath(filename); + auto path = filepath(filename); auto file = FileFS.open(path, "r"); if (!file) { - SerialPrint(F("E"), F("FS"), F("reed file error")); + SerialPrint("E", "FS", F("reed file error")); return; } - size_t fileSize = file.size(); - SerialPrint(F("i"), F("FS"), "Send file '" + String(filename) + "', file size: " + String(fileSize)); - uint8_t payload[frameSize]; - int countRead = file.read(payload, sizeof(payload)); - while (countRead > 0) { - if (num == -1) { - standWebSocket.broadcastBIN(payload, countRead); + + size_t totalSize = file.size(); + // Serial.println("Send file '" + String(filename) + "', file size: " + String(totalSize)); + + char buf[32]; + sprintf(buf, "%04d", json.length() + 12); + String data = header + "|" + String(buf) + "|" + json; + + size_t headerSize = data.length(); + auto frameBuf = new uint8_t[frameSize]; + size_t maxPayloadSize = frameSize - headerSize; + uint8_t* payloadBuf = nullptr; + + int i = 0; + while (file.available()) { + if (i == 0) { + data.toCharArray((char*)frameBuf, frameSize); + payloadBuf = &frameBuf[headerSize]; } else { - standWebSocket.sendBIN(num, payload, countRead); + maxPayloadSize = frameSize; + headerSize = 0; + payloadBuf = &frameBuf[0]; } - countRead = file.read(payload, sizeof(payload)); + + size_t payloadSize = file.read(payloadBuf, maxPayloadSize); + if (payloadSize) { + size_t size = headerSize + payloadSize; + + bool fin = false; + if (size == frameSize) { + fin = false; + } else { + fin = true; + } + + bool continuation = false; + if (i == 0) { + continuation = false; + } else { + continuation = true; + } + + // Serial.println(String(i) + ") " + "ws: " + String(client_id) + " fr sz: " + String(size) + " fin: " + String(fin) + " cnt: " + String(continuation)); + + if (client_id == -1) { + standWebSocket.broadcastBIN(frameBuf, size, fin, continuation); + + } else { + standWebSocket.sendBIN(client_id, frameBuf, size, fin, continuation); + } + } + i++; } + payloadBuf = &frameBuf[0]; + delete[] payloadBuf; file.close(); - String end = "/end/chart.json|" + json; - if (num == -1) { - standWebSocket.broadcastTXT(end); - } else { - standWebSocket.sendTXT(num, end); - } } -// void sendMark(const char* filename, const char* mark, uint8_t num) { -// char outChar[strlen(filename) + strlen(mark) + 1]; -// strcpy(outChar, mark); -// strcat(outChar, filename); -// size_t size = strlen(outChar); -// uint8_t outUint[size]; -// for (size_t i = 0; i < size; i++) { -// outUint[i] = uint8_t(outChar[i]); -// } -// standWebSocket.sendBIN(num, outUint, sizeof(outUint)); -// } +void sendStringToWs(const String& header, String& payload, int client_id) { + if (header.length() != 6) { + SerialPrint("E", "FS", F("wrong header size")); + return; + } -//посылка данных из файла в string -// void sendFileToWs3(const String& filename, uint8_t num) { -// standWebSocket.sendTXT(num, "/st" + filename); -// size_t ws_buffer = 512; -// String path = filepath(filename); -// auto file = FileFS.open(path, "r"); -// if (!file) { -// SerialPrint(F("E"), F("FS"), F("reed file error")); -// } -// size_t fileSize = file.size(); -// SerialPrint(F("i"), F("WS"), "Send file '" + filename + "', file size: " + String(fileSize)); -// String ret; -// char temp[ws_buffer + 1]; -// int countRead = file.readBytes(temp, sizeof(temp) - 1); -// while (countRead > 0) { -// temp[countRead] = 0; -// ret = temp; -// standWebSocket.sendTXT(num, ret); -// countRead = file.readBytes(temp, sizeof(temp) - 1); -// } -// standWebSocket.sendTXT(num, "/end" + filename); -//} + String msg = header + "|0012|" + payload; + size_t totalSize = msg.length(); + // Serial.println("Send string '" + header + "', str size: " + String(totalSize)); -//посылка данных из файла в char -// void sendFileToWs4(const String& filename, uint8_t num) { -// standWebSocket.sendTXT(num, "/st" + filename); -// size_t ws_buffer = 512; -// String path = filepath(filename); -// auto file = FileFS.open(path, "r"); -// if (!file) { -// SerialPrint(F("E"), F("FS"), F("reed file error")); -// } -// size_t fileSize = file.size(); -// SerialPrint(F("i"), F("WS"), "Send file '" + filename + "', file size: " + String(fileSize)); -// char temp[ws_buffer + 1]; -// int countRead = file.readBytes(temp, sizeof(temp) - 1); -// while (countRead > 0) { -// temp[countRead] = 0; -// standWebSocket.sendTXT(num, temp, countRead); -// countRead = file.readBytes(temp, sizeof(temp) - 1); -// } -//} + char dataArray[totalSize]; + msg.toCharArray(dataArray, totalSize + 1); + if (client_id == -1) { + standWebSocket.broadcastBIN((uint8_t*)dataArray, totalSize); + } else { + standWebSocket.sendBIN(client_id, (uint8_t*)dataArray, totalSize); + } +} diff --git a/src/classes/IoTItem.cpp b/src/classes/IoTItem.cpp index 68f88c96..095e6c18 100644 --- a/src/classes/IoTItem.cpp +++ b/src/classes/IoTItem.cpp @@ -6,11 +6,11 @@ #include "EventsAndOrders.h" //получение параметров в экземпляр класса -IoTItem::IoTItem(String parameters) { - jsonRead(parameters, F("int"), _interval); - if (_interval == 0) enableDoByInt = false; +IoTItem::IoTItem(const String& parameters) { + jsonRead(parameters, F("int"), _interval, false); + if (_interval <= 0) enableDoByInt = false; _interval = _interval * 1000; - jsonRead(parameters, F("subtype"), _subtype); + jsonRead(parameters, F("subtype"), _subtype, false); jsonRead(parameters, F("id"), _id); if (!jsonRead(parameters, F("multiply"), _multiply, false)) _multiply = 1; if (!jsonRead(parameters, F("plus"), _plus, false)) _plus = 0; @@ -18,14 +18,6 @@ IoTItem::IoTItem(String parameters) { if (!jsonRead(parameters, F("global"), _global, false)) _global = false; - String valAsStr; - if (jsonRead(parameters, F("val"), valAsStr, false)) // значение переменной или датчика при инициализации если есть в конфигурации - if (value.isDecimal = isDigitDotCommaStr(valAsStr)) { - value.valD = valAsStr.toFloat(); - } else { - value.valS = valAsStr; - } - String map; jsonRead(parameters, F("map"), map, false); if (map != "") { @@ -35,6 +27,14 @@ IoTItem::IoTItem(String parameters) { _map4 = selectFromMarkerToMarker(map, ",", 3).toInt(); } else _map1 = _map2 = _map3 = _map4 = 0; + + String valAsStr = ""; + if (jsonRead(parameters, F("val"), valAsStr, false)) // значение переменной или датчика при инициализации если есть в конфигурации + setValue(valAsStr, false); + + jsonRead(parameters, F("needSave"), _needSave, false); + if (_needSave && jsonRead(valuesFlashJson, _id, valAsStr, false)) // пробуем достать из сохранения значение элемента, если указано, что нужно сохранять + setValue(valAsStr, false); } //луп выполняющий переодическое дерганье @@ -52,82 +52,129 @@ void IoTItem::loop() { //получить String IoTItem::getValue() { if (value.isDecimal) { - if (_multiply) value.valD = value.valD * _multiply; - if (_plus) value.valD = value.valD + _plus; - if (_round >= 0 && _round <= 6) { - int sot = _round ? pow(10, (int)_round) : 1; - value.valD = round(value.valD * sot) / sot; - } - if (_map1 != _map2) value.valD = map(value.valD, _map1, _map2, _map3, _map4); - - if (_round >= 0 && _round <= 6) { - char buf[15]; - sprintf(buf, ("%1." + (String)_round + "f").c_str(), value.valD); - return value.valS = buf; - } else - return (String)value.valD; + return getRoundValue(); } else return value.valS; } +long IoTItem::getInterval() { return _interval; } + +bool IoTItem::isGlobal() { return _global;} + //определяем тип прилетевшей величины -void IoTItem::setValue(String valStr) { - if (value.isDecimal = isDigitDotCommaStr(valStr)) { +void IoTItem::setValue(const String& valStr, bool genEvent) { + value.isDecimal = isDigitDotCommaStr(valStr); + + if (value.isDecimal) { value.valD = valStr.toFloat(); } else { value.valS = valStr; } - setValue(value); + setValue(value, genEvent); } // -void IoTItem::setValue(IoTValue Value) { +void IoTItem::setValue(const IoTValue& Value, bool genEvent) { value = Value; + if (value.isDecimal) { - regEvent(value.valD, ""); + regEvent(value.valD, "", false, genEvent); } else { - regEvent(value.valS, ""); + regEvent(value.valS, "", false, genEvent); } } //когда событие случилось -void IoTItem::regEvent(String value, String consoleInfo = "") { - generateEvent(_id, value); +void IoTItem::regEvent(const String& value, const String& consoleInfo, bool error, bool genEvent) { + if (_needSave) { + jsonWriteStr_(valuesFlashJson, _id, value); + needSaveValues = true; + } publishStatusMqtt(_id, value); - publishStatusWs(_id, value); - SerialPrint("i", "Sensor", consoleInfo + " '" + _id + "' data: " + value + "'"); + //SerialPrint("i", "Sensor", consoleInfo + " '" + _id + "' data: " + value + "'"); + + if (genEvent) { + generateEvent(_id, value); - // проверка если global установлен то шлем всем о событии - // if (_global) { - // SerialPrint("i", F("=>ALLMQTT"), "Broadcast event: "); - // } - //отправка события другим устройствам в сети============================== - // if (jsonReadBool(settingsFlashJson, "mqttin")) { - // String json = "{}"; - // jsonWriteStr_(json, "id", _id); - // jsonWriteStr_(json, "val", value); - // jsonWriteInt_(json, "int", _interval + 5000); // 5 секунд про запас - // publishEvent(_id, json); - // SerialPrint("i", F("<=MQTT"), "Broadcast event: " + json); - //} - //======================================================================== + // распространяем событие через хуки + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { + (*it)->onRegEvent(this); + } + + //отправка события другим устройствам в сети если не было ошибки============================== + if (jsonReadBool(settingsFlashJson, "mqttin") && _global && !error) { + String json = "{}"; + jsonWriteStr_(json, "id", _id); + jsonWriteStr_(json, "val", value); + jsonWriteInt_(json, "int", _interval/1000); + publishEvent(_id, json); + SerialPrint("i", F("<=MQTT"), "Broadcast event: " + json); + } + //======================================================================== + } } -void IoTItem::regEvent(float regvalue, String consoleInfo = "") { +String IoTItem::getRoundValue() { + if (_round >= 0 && _round <= 6) { + int sot = _round ? pow(10, (int)_round) : 1; + value.valD = round(value.valD * sot) / sot; + + char buf[15]; + sprintf(buf, ("%1." + (String)_round + "f").c_str(), value.valD); + return (String)buf; + } else { + return (String)value.valD; + } +} + +void IoTItem::regEvent(float regvalue, const String& consoleInfo, bool error, bool genEvent) { value.valD = regvalue; - regEvent(getValue(), consoleInfo); + + if (_multiply) value.valD = value.valD * _multiply; + if (_plus) value.valD = value.valD + _plus; + if (_map1 != _map2) value.valD = map(value.valD, _map1, _map2, _map3, _map4); + + regEvent(getRoundValue(), consoleInfo, error, genEvent); } void IoTItem::doByInterval() {} IoTValue IoTItem::execute(String command, std::vector& param) { return {}; } -//захрена эта хрень? - самому пригодилась сорян Илья String IoTItem::getSubtype() { return _subtype; } +int IoTItem::getIntFromNet() { + return _intFromNet; +} + +void IoTItem::getNetEvent(String& event) { + event = "{}"; + jsonWriteStr_(event, "id", _id); + jsonWriteStr_(event, "val", getValue()); + jsonWriteInt_(event, "int", _interval/1000); +} + +void IoTItem::setIntFromNet(int interval) { + _intFromNet = interval; +} + +void IoTItem::checkIntFromNet() { + // проверяем элемент на доверие данным. + if (_intFromNet >= 0) { + // если время жизни истекло, то удаляем элемент + // если это было уведомление не об ошибке или начале работы, то сообщаем, что сетевое событие давно не приходило + if (_intFromNet == 0 && _id.indexOf("onError") == -1 && _id.indexOf("onStart") == -1) { + SerialPrint("E", _id, "The new data did not come from the network. The level of trust is low.", _id); + } + _intFromNet--; + } +} + +void IoTItem::onRegEvent(IoTItem* item) {} + void IoTItem::publishValue() {} void IoTItem::clearValue() {} @@ -142,7 +189,7 @@ String IoTItem::getID() { return _id; }; -void IoTItem::setInterval(unsigned long interval) { +void IoTItem::setInterval(long interval) { _interval = interval; } @@ -150,28 +197,30 @@ IoTGpio* IoTItem::getGpioDriver() { return nullptr; } + //сетевое общение==================================================================================================================================== -externalVariable::externalVariable(String parameters) : IoTItem(parameters) { - prevMillis = millis(); // запоминаем текущее значение таймера для выполения doByInterval после int сек - iAmLocal = false; // указываем, что это сущность прилетела из сети - Serial.printf("Call from externalVariable: parameters %s %d\n", parameters.c_str(), _interval); -} +// externalVariable::externalVariable(const String& parameters) : IoTItem(parameters) { +// prevMillis = millis(); // запоминаем текущее значение таймера для выполения doByInterval после int сек +// iAmLocal = false; // указываем, что это сущность прилетела из сети +// //Serial.printf("Call from externalVariable: parameters %s %d\n", parameters.c_str(), _interval); +// } -externalVariable::~externalVariable() { - Serial.printf("Call from ~externalVariable: Im dead\n"); -} +// externalVariable::~externalVariable() { +// Serial.printf("Call from ~externalVariable: Im dead\n"); +// } -void externalVariable::doByInterval() { // для данного класса doByInterval+int выполняет роль счетчика обратного отсчета до уничтожения - iAmDead = true; -} +// void externalVariable::doByInterval() { // для данного класса doByInterval+int выполняет роль счетчика обратного отсчета до уничтожения +// iAmDead = true; +// } //========================================================================================================================================= IoTItem* myIoTItem; // поиск элемента модуля в существующей конфигурации -IoTItem* findIoTItem(String name) { +IoTItem* findIoTItem(const String& name) { + if (name == "") return nullptr; for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { if ((*it)->getID() == name) return *it; } @@ -179,7 +228,7 @@ IoTItem* findIoTItem(String name) { return nullptr; } // поиск плюс получение значения -String getItemValue(String name) { +String getItemValue(const String& name) { IoTItem* tmp = findIoTItem(name); if (tmp) return tmp->getValue(); @@ -188,7 +237,7 @@ String getItemValue(String name) { } // существует ли айтем -bool isItemExist(String name) { +bool isItemExist(const String& name) { IoTItem* tmp = findIoTItem(name); if (tmp) return true; @@ -196,6 +245,48 @@ bool isItemExist(String name) { return false; } +// создаем временную копию элемента из сети на основе события +IoTItem* createItemFromNet(const String& itemId, const String& value, int interval) { + String jsonStr = "{\"id\":\""; + jsonStr += itemId; + jsonStr += "\",\"val\":\""; + jsonStr += value; + jsonStr += "\",\"int\":"; + jsonStr += interval; + jsonStr += "}"; + + return createItemFromNet(jsonStr); +} + +// создаем временную копию элемента из сети на основе события +IoTItem* createItemFromNet(const String& msgFromNet) { + IoTItem *tmpp = new IoTItem(msgFromNet); + if (tmpp->getInterval()) tmpp->setIntFromNet(tmpp->getInterval()/1000 + 5); + tmpp->iAmLocal = false; + IoTItems.push_back(tmpp); + generateEvent(tmpp->getID(), tmpp->getValue()); + return tmpp; +} + +void analyzeMsgFromNet(const String& msg, String altId) { + if (!jsonRead(msg, F("id"), altId, altId == "") && altId == "") return; // ничего не предпринимаем, если ошибка и altId = "", вообще данная конструкция нужна для совместимости с форматом данных 3 версией + IoTItem* itemExist = findIoTItem(altId); + if (itemExist) { + String valAsStr = msg; + jsonRead(msg, F("val"), valAsStr, false); + long interval = 0; + jsonRead(msg, F("int"), interval, false); + + itemExist->setInterval(interval); // устанавливаем такой же интервал как на источнике события + itemExist->setValue(valAsStr, false); // только регистрируем изменения в интерфейсе без создания цикла сетевых событий + if (interval) itemExist->setIntFromNet(interval+5); // если пришедший интервал =0, значит не нужно контролировать доверие, иначе даем фору в 5 сек + generateEvent(altId, valAsStr); + } else { + // временно зафиксируем данные в базе, если локально элемент отсутствует + createItemFromNet(msg); + } +} + StaticJsonDocument docForExport; StaticJsonDocument* getLocalItemsAsJSON() { diff --git a/src/classes/IoTScenario.cpp b/src/classes/IoTScenario.cpp index 5d44c832..39d4954a 100644 --- a/src/classes/IoTScenario.cpp +++ b/src/classes/IoTScenario.cpp @@ -21,6 +21,7 @@ enum Token { tok_notequal = -9, tok_lesseq = -10, tok_greateq = -11, + tok_silentset = -12, // управление tok_if = -6, @@ -35,8 +36,8 @@ enum Token { /// ExprAST - Базовый класс для всех узлов выражений. ExprAST::~ExprAST() {} IoTValue *ExprAST::exec() { return nullptr; } -int ExprAST::setValue(IoTValue *val) { return 0; } // 0 - установка значения не поддерживается наследником -bool ExprAST::hasEventIdName(String eventIdName) { return false; } // по умолчанию все узлы не связаны с ИД события, для которого выполняется сценарий +int ExprAST::setValue(IoTValue *val, bool generateEvent) { return 0; } // 0 - установка значения не поддерживается наследником +bool ExprAST::hasEventIdName(const String& eventIdName) { return false; } // по умолчанию все узлы не связаны с ИД события, для которого выполняется сценарий // struct IoTValue zeroIotVal; /// NumberExprAST - Класс узла выражения для числовых литералов (Например, "1.0"). @@ -77,17 +78,17 @@ class StringExprAST : public ExprAST { class VariableExprAST : public ExprAST { String Name; IoTItem *Item; // ссылка на объект модуля (прямой доступ к идентификатору указанному в сценарии), если получилось найти модуль по ID - bool ItemIsLocal = false; + //bool ItemIsLocal = false; public: VariableExprAST(const String &name, IoTItem *item) : Name(name), Item(item) { - if (item) ItemIsLocal = item->iAmLocal; + //if (item) ItemIsLocal = item->iAmLocal; } - int setValue(IoTValue *val) { - if (!ItemIsLocal) Item = findIoTItem(Name); + int setValue(IoTValue *val, bool generateEvent) { + //if (!ItemIsLocal) Item = findIoTItem(Name); if (Item) - Item->setValue(*val); + Item->setValue(*val, generateEvent); else return 0; @@ -96,7 +97,7 @@ class VariableExprAST : public ExprAST { IoTValue *exec() { if (isIotScenException) return nullptr; - if (!ItemIsLocal) Item = findIoTItem(Name); + //if (!ItemIsLocal) Item = findIoTItem(Name); if (Item) { // if (Item->value.isDecimal) // Serial.printf("Call from VariableExprAST: %s = %f\n", Name.c_str(), Item->value.valD); @@ -104,6 +105,7 @@ class VariableExprAST : public ExprAST { return &(Item->value); } + SerialPrint("E", Name, "The element is not found or the connection is lost", Name); return nullptr; // Item не найден. } }; @@ -145,90 +147,98 @@ class BinaryExprAST : public ExprAST { if (RHS == nullptr || LHS == nullptr) return nullptr; IoTValue *rhs = RHS->exec(); // получаем значение правого операнда для возможного использования в операции присваивания + if (rhs == nullptr) return nullptr; - if (Op == '=' && LHS->setValue(rhs)) { // если установка значения не поддерживается, т.е. слева не переменная, то работаем по другим комбинациям далее + if (Op == '=' && LHS->setValue(rhs, true)) { // если установка значения не поддерживается, т.е. слева не переменная, то работаем по другим комбинациям далее + return rhs; // иначе возвращаем присвоенное значение справа + } + + if (Op == tok_silentset && LHS->setValue(rhs, false)) { // если установка значения не поддерживается, т.е. слева не переменная, то работаем по другим комбинациям далее return rhs; // иначе возвращаем присвоенное значение справа } IoTValue *lhs = LHS->exec(); // если присваивания не произошло, значит операция иная и необходимо значение левого операнда + if (lhs == nullptr) return nullptr; - if (lhs != nullptr && rhs != nullptr) { - if (lhs->isDecimal && rhs->isDecimal) { - switch (Op) { - case '>': - val.valD = lhs->valD > rhs->valD; - break; - case '<': - val.valD = lhs->valD < rhs->valD; - break; - case tok_lesseq: - val.valD = lhs->valD <= rhs->valD; - break; - case tok_greateq: - val.valD = lhs->valD >= rhs->valD; - break; - case tok_equal: - val.valD = lhs->valD == rhs->valD; - break; - case tok_notequal: - val.valD = lhs->valD != rhs->valD; - break; + + if (lhs->isDecimal && rhs->isDecimal) { + switch (Op) { + case '>': + val.valD = lhs->valD > rhs->valD; + break; + case '<': + val.valD = lhs->valD < rhs->valD; + break; + case tok_lesseq: + val.valD = lhs->valD <= rhs->valD; + break; + case tok_greateq: + val.valD = lhs->valD >= rhs->valD; + break; + case tok_equal: + val.valD = lhs->valD == rhs->valD; + break; + case tok_notequal: + val.valD = lhs->valD != rhs->valD; + break; - case '+': - val.valD = lhs->valD + rhs->valD; - break; - case '-': - val.valD = lhs->valD - rhs->valD; - break; - case '*': - val.valD = lhs->valD * rhs->valD; - break; - case '/': - if (rhs->valD != 0) - val.valD = lhs->valD / rhs->valD; - else - val.valD = 3.4E+38; - break; + case '+': + val.valD = lhs->valD + rhs->valD; + break; + case '-': + val.valD = lhs->valD - rhs->valD; + break; + case '*': + val.valD = lhs->valD * rhs->valD; + break; + case '/': + if (rhs->valD != 0) + val.valD = lhs->valD / rhs->valD; + else + val.valD = 3.4E+38; + break; - case '|': - val.valD = lhs->valD || rhs->valD; - break; - case '&': - val.valD = lhs->valD && rhs->valD; - break; + case '|': + val.valD = lhs->valD || rhs->valD; + break; + case '&': + val.valD = lhs->valD && rhs->valD; + break; - default: - break; - } - return &val; - } - - if (!lhs->isDecimal || !rhs->isDecimal) { - if (lhs->isDecimal) - lhsStr = (String)lhs->valD; - else - lhsStr = lhs->valS; - if (rhs->isDecimal) - rhsStr = (String)rhs->valD; - else - rhsStr = rhs->valS; - switch (Op) { - case tok_equal: - val.valD = compStr(lhsStr, rhsStr); - break; - - case '+': - val.valS = lhsStr + rhsStr; - val.valD = 1; - val.isDecimal = false; - break; - - default: - break; - } - return &val; + default: + break; } + return &val; } + + if (!lhs->isDecimal || !rhs->isDecimal) { + if (lhs->isDecimal) + lhsStr = (String)lhs->valD; + else + lhsStr = lhs->valS; + + if (rhs->isDecimal) + rhsStr = (String)rhs->valD; + else + rhsStr = rhs->valS; + + switch (Op) { + case tok_equal: + val.valD = compStr(lhsStr, rhsStr); + break; + + case '+': + val.valS = lhsStr + rhsStr; + val.valD = 1; + val.isDecimal = false; + break; + + default: + break; + } + return &val; + } + return &val; } @@ -250,12 +260,12 @@ class CallExprAST : public ExprAST { std::vector Args; IoTItem *Item; // ссылка на объект модуля (прямой доступ к идентификатору указанному в сценарии), если получилось найти модуль по ID IoTValue ret; // хранение возвращаемого значения, т.к. возврат по ссылке осуществляется - bool ItemIsLocal = false; + //bool ItemIsLocal = false; public: CallExprAST(const String &callee, String &cmd, std::vector &args, IoTItem *item) : Callee(callee), Cmd(cmd), Args(args), Item(item) { - if (item) ItemIsLocal = item->iAmLocal; + //if (item) ItemIsLocal = item->iAmLocal; } IoTValue *exec() { @@ -273,9 +283,15 @@ class CallExprAST : public ExprAST { return nullptr; } - if (!ItemIsLocal) Item = findIoTItem(Callee); // пробуем найти переменную если она не локальная (могла придти по сети в процессе) + //if (!ItemIsLocal) Item = findIoTItem(Callee); // пробуем найти переменную если она не локальная (могла придти по сети в процессе) if (!Item) return nullptr; // ret = zeroIotVal; // если все же не пришла, то либо опечатка, либо уже стерлась - выходим + if (Cmd == "getIntFromNet") { + ret.valD = Item->getIntFromNet(); + ret.isDecimal = true; + return &ret; + } + // если все же все ок, то готовим параметры для передачи в модуль std::vector ArgsAsIoTValue; for (unsigned int i = 0; i < Args.size(); i++) { @@ -321,6 +337,7 @@ enum SysOp { sysop_gethhmm, sysop_gethhmmss, sysop_getTime, + sysop_getRSSI, sysop_getIP, sysop_mqttPub, sysop_getUptime @@ -404,6 +421,10 @@ IoTValue sysExecute(SysOp command, std::vector ¶m) { #endif } break; + case sysop_getRSSI: + value.valD = WiFi.RSSI(); + value.isDecimal = true; + break; case sysop_getIP: value.valS = jsonReadStr(settingsFlashJson, F("ip")); value.isDecimal = false; @@ -411,7 +432,9 @@ IoTValue sysExecute(SysOp command, std::vector ¶m) { case sysop_mqttPub: if (param.size() == 2) { // Serial.printf("Call from sysExecute %s %s\n", param[0].valS.c_str(), param[1].valS.c_str()); - value.valD = mqtt.publish(param[0].valS.c_str(), param[1].valS.c_str(), false); + String tmpStr = param[1].valS; + if (param[1].isDecimal) tmpStr = param[1].valD; + value.valD = mqtt.publish(param[0].valS.c_str(), tmpStr.c_str(), false); } break; case sysop_getUptime: @@ -458,6 +481,8 @@ class SysCallExprAST : public ExprAST { operation = sysop_getMonth; else if (Callee == "getDay") operation = sysop_getDay; + else if (Callee == "getRSSI") + operation = sysop_getRSSI; else if (Callee == "getIP") operation = sysop_getIP; else if (Callee == "mqttPub") @@ -518,7 +543,7 @@ class IfExprAST : public ExprAST { _IDNames = ""; } - bool hasEventIdName(String eventIdName) { + bool hasEventIdName(const String& eventIdName) { // Serial.printf("Call from BinaryExprAST _IDNames:%s\n", _IDNames.c_str()); return _IDNames.indexOf(" " + eventIdName + " ") >= 0; // определяем встречался ли ИД, для которого исполняем сценарий в выражении IF } @@ -535,7 +560,8 @@ class IfExprAST : public ExprAST { return nullptr; //&zeroIotVal; } - if (cond_ret->isDecimal && cond_ret->valD) { + // если число больше нуля или строка не равна пустой, то считаем условие выполненным + if (cond_ret->isDecimal && cond_ret->valD || !(cond_ret->isDecimal) && cond_ret->valS != "") { if (Then == nullptr) return nullptr; res_ret = Then->exec(); } else { @@ -546,7 +572,7 @@ class IfExprAST : public ExprAST { // if (!res_ret) Serial.printf("Call from IfExprAST: Cond result = %f, no body result\n", cond_ret->valD); // else if (res_ret->isDecimal) Serial.printf("Call from IfExprAST: Cond result = %f, result = %f\n", cond_ret->valD, res_ret->valD); // else Serial.printf("Call from IfExprAST: Cond result = %f, result = %s\n", cond_ret->valD, res_ret->valS.c_str()); - Serial.printf("\n"); + //Serial.printf("\n"); return cond_ret; } @@ -686,6 +712,15 @@ int IoTScenario::gettok() { return '='; } + if (LastChar == ':') { + LastChar = getLastChar(); + if (LastChar == '=') { + LastChar = getLastChar(); + return tok_silentset; + } else + return ':'; + } + if (LastChar == '!') { LastChar = getLastChar(); if (LastChar == '=') { @@ -991,7 +1026,7 @@ void IoTScenario::loadScenario(String fileName) { // подготавливае } } -void IoTScenario::exec(String eventIdName) { // посимвольно считываем и сразу интерпретируем сценарий в дерево AST +void IoTScenario::exec(const String& eventIdName) { // посимвольно считываем и сразу интерпретируем сценарий в дерево AST if (mode == 0 && !file) return; LastChar = 0; @@ -1027,7 +1062,8 @@ void IoTScenario::exec(String eventIdName) { // посимвольно счит IoTScenario::IoTScenario() { // Задаём стандартные бинарные операторы. // 1 - наименьший приоритет. - BinopPrecedence['='] = 1; + BinopPrecedence[tok_silentset] = 1; + BinopPrecedence['='] = 2; BinopPrecedence['|'] = 5; BinopPrecedence['&'] = 6; BinopPrecedence[tok_equal] = 10; // == diff --git a/src/modules/API.cpp b/src/modules/API.cpp index 38b509e8..2935ae36 100644 --- a/src/modules/API.cpp +++ b/src/modules/API.cpp @@ -1,5 +1,6 @@ #include "ESPConfiguration.h" +void* getAPI_Cron(String subtype, String params); void* getAPI_Loging(String subtype, String params); void* getAPI_LogingDaily(String subtype, String params); void* getAPI_Timer(String subtype, String params); @@ -12,7 +13,6 @@ 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_FreqMeter(String subtype, String params); void* getAPI_GY21(String subtype, String params); void* getAPI_Hdc1080(String subtype, String params); void* getAPI_Max6675(String subtype, String params); @@ -27,13 +27,16 @@ void* getAPI_ButtonOut(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_TelegramLT(String subtype, String params); void* getAPI_Lcd2004(String subtype, String params); +void* getAPI_Ws2812b(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_Timer(subtype, params)) != nullptr) return tmpAPI; @@ -46,7 +49,6 @@ 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_FreqMeter(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_GY21(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_Hdc1080(subtype, params)) != nullptr) return tmpAPI; if ((tmpAPI = getAPI_Max6675(subtype, params)) != nullptr) return tmpAPI; @@ -61,9 +63,11 @@ if ((tmpAPI = getAPI_ButtonOut(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_Lcd2004(subtype, params)) != nullptr) return tmpAPI; +if ((tmpAPI = getAPI_Ws2812b(subtype, params)) != nullptr) return tmpAPI; return nullptr; } \ No newline at end of file diff --git a/src/modules/display/Lcd2004/Lcd2004.cpp b/src/modules/display/Lcd2004/Lcd2004.cpp index 3feee118..968bed0b 100644 --- a/src/modules/display/Lcd2004/Lcd2004.cpp +++ b/src/modules/display/Lcd2004/Lcd2004.cpp @@ -15,16 +15,17 @@ class Lcd2004 : public IoTItem { String _id2show; String _descr; int _prevStrSize; + String _addr; bool _isShow = true; // экран показывает public: Lcd2004(String parameters) : IoTItem(parameters) { - String addr, size, xy; + String size, xy; _prevStrSize = 0; - jsonRead(parameters, "addr", addr); - if (addr == "") { + jsonRead(parameters, "addr", _addr); + if (_addr == "") { scanI2C(); return; } @@ -33,12 +34,14 @@ class Lcd2004 : public IoTItem { int w = selectFromMarkerToMarker(size, ",", 0).toInt(); //количество столбцов int h = selectFromMarkerToMarker(size, ",", 1).toInt(); //количество строк if (LCDI2C == nullptr) { //инициализации экрана еще не было - LCDI2C = new LiquidCrystal_I2C(hexStringToUint8(addr), w, h); + LCDI2C = new LiquidCrystal_I2C(hexStringToUint8(_addr), w, h); if (LCDI2C != nullptr) { LCDI2C->init(); - LCDI2C->backlight(); } } + + LCDI2C->clear(); + LCDI2C->backlight(); jsonRead(parameters, "coord", xy); _x = selectFromMarkerToMarker(xy, ",", 0).toInt(); @@ -52,15 +55,16 @@ class Lcd2004 : public IoTItem { if (LCDI2C != nullptr) { printBlankStr(_prevStrSize); - String tmpStr = ""; - if (_descr != "none") tmpStr = _descr + " " + getItemValue(_id2show); - else tmpStr = getItemValue(_id2show); + String tmpStr = getItemValue(_id2show); + if (_descr != "none") tmpStr = _descr + " " + tmpStr; LCDI2C->setCursor(_x, _y); LCDI2C->print(tmpStr); //LCDI2C->print("Helloy,Manager 404 !"); - + //Serial.printf("ffff %s\n", _id2show); _prevStrSize = tmpStr.length(); + } else { + scanI2C(); } } @@ -113,7 +117,10 @@ class Lcd2004 : public IoTItem { LCDI2C->print(tmpStr); } - ~Lcd2004(){}; + ~Lcd2004(){ + if (LCDI2C) delete LCDI2C; + LCDI2C = nullptr; + }; }; void *getAPI_Lcd2004(String subtype, String param) { diff --git a/src/modules/display/Lcd2004/modinfo.json b/src/modules/display/Lcd2004/modinfo.json index 0352ab51..914d9047 100644 --- a/src/modules/display/Lcd2004/modinfo.json +++ b/src/modules/display/Lcd2004/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Экраны", "configItem": [{ + "global": 0, "name": "LCD экран 2004", "type": "Reading", "subtype": "Lcd2004", @@ -108,6 +109,12 @@ ], "esp8266_4mb": [ "marcoschwartz/LiquidCrystal_I2C@^1.1.4" + ], + "esp8266_1mb": [ + "marcoschwartz/LiquidCrystal_I2C@^1.1.4" + ], + "esp8266_1mb_ota": [ + "marcoschwartz/LiquidCrystal_I2C@^1.1.4" ] } } \ No newline at end of file diff --git a/src/modules/display/Ws2812b/Ws2181b.cpp b/src/modules/display/Ws2812b/Ws2181b.cpp new file mode 100644 index 00000000..0dbf5362 --- /dev/null +++ b/src/modules/display/Ws2812b/Ws2181b.cpp @@ -0,0 +1,198 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include "ESPConfiguration.h" +#include + + +class Ws2812b : public IoTItem +{ +private: + Adafruit_NeoPixel *_strip; + int _pin; + int _numLeds; + int _brightness; + int correctLed; + int _min = 0; + int _max = 100; + int PrevValidShow = 0; + int FlagFN = 1; + int PreFlagFN = 1; + String idshow; + +public: + Ws2812b(String parameters) : IoTItem(parameters) { + jsonRead(parameters, F("pin"), _pin); + jsonRead(parameters, F("numLeds"), _numLeds); + jsonRead(parameters, F("idshow"), idshow); + jsonRead(parameters, F("brightness"), _brightness); + jsonRead(parameters, F("min"), _min); + jsonRead(parameters, F("max"), _max); + + + _strip = new Adafruit_NeoPixel(_numLeds, _pin, NEO_GRB + NEO_KHZ800); + if (_strip != nullptr) { + _strip->begin(); + SerialPrint("E", "Strip Ws2812b:" + _id, "begin"); + correctLed = correctPixel(_numLeds); + _strip->setBrightness(_brightness); + _strip->clear(); + } + } + + // void loop() { + // if (enableDoByInt) { + // currentMillis = millis(); + // difference = currentMillis - prevMillis; + // if (difference >= interval || PreFlagFN != FlagFN) { + // prevMillis = millis(); + // this->doByInterval(); + // } + // } + // } + + void doByInterval() { + if (!_strip) return; + + if (!isItemExist(idshow)) { + SerialPrint("E", F("Ws2812b"), "'" + idshow + "' detector object not exist"); + }else if (getItemValue(idshow) == ""){ + SerialPrint("E", F("Ws2812b"), "'" + idshow + "' detector value is empty"); + }else if (_min >= _max){ + SerialPrint("E", F("Ws2812b"), " the minimum (" + String(_min) + ") value must be greater than the maximum (" + String(_max) + ")"); + }else if(isItemExist(idshow) && getItemValue(idshow) != "" && _min < _max && FlagFN == 1){ + SerialPrint("E", "Ws2812b:" + String(correctLed), " work"); + String value = getItemValue(idshow); + if(PrevValidShow == 0 || PrevValidShow > value.toInt() ){ + noShow(); + } + int t = map(value.toInt(), _min, _max, 0, _numLeds); + for(uint16_t L = 0; LsetPixelColor(L,wheel(((205+(L*correctLed)) & 255))); + } + PrevValidShow = value.toInt(); + _strip->show(); + } + } + + int correctPixel(int _numLeds){ + if(_numLeds <= 65 && _numLeds > 60){ + correctLed = 0; + }else if(_numLeds <= 60 && _numLeds > 55){ + correctLed = 1; + }else if(_numLeds <= 55 && _numLeds > 50){ + correctLed = 2; + }else if(_numLeds <= 50 && _numLeds > 40){ + correctLed = 3; + }else if(_numLeds <= 40 && _numLeds > 35){ + correctLed = 4; + }else if(_numLeds <= 35 && _numLeds > 24){ + correctLed = 5; + }else if(_numLeds <= 24 && _numLeds > 16){ + correctLed = 6; + }else if(_numLeds <= 16 && _numLeds > 12){ + correctLed = 8; + }else if(_numLeds <= 12 && _numLeds > 8){ + correctLed = 12; + }else if(_numLeds <= 8 && _numLeds > 4){ + correctLed = 16; + }else{ + correctLed = 0; + } + return correctLed; + } + + uint32_t wheel(byte WheelPos) { + if(WheelPos < 85) { + return _strip->Color(WheelPos * 3, 255 - WheelPos * 3, 0); + } + else if(WheelPos < 205) { + WheelPos -= 85; + return _strip->Color(255 - WheelPos * 3, 0, WheelPos * 3); + } + else { + WheelPos -= 205; + return _strip->Color(0, WheelPos * 3, 255 - WheelPos * 3); + } + } + + void noShow(){ + if (!_strip) return; + + _strip->clear(); + for(int i=0; i<_numLeds; i++) { + _strip->setPixelColor(i, _strip->Color(0, 0, 0)); + _strip->show(); + } + } + + IoTValue execute(String command, std::vector ¶m) { + if (!_strip) return {}; + + if (command == "test") { + for(int i=0; i<_numLeds; i++) { + _strip->setPixelColor(i, _strip->Color(20+(i*2), 20+(i*2), 20+(i*2))); + _strip->show(); + } + SerialPrint("E", "Strip Ws2812b", "demo"); + } else if (command == "noShow"){ + noShow(); + SerialPrint("E", "Strip Ws2812b", "noShow"); + } else if(command == "noShowOne"){ + if (param.size() == 1) { + _strip->setPixelColor(param[0].valD, _strip->Color(0, 0, 0)); + _strip->show(); + SerialPrint("E", "Strip Ws2812b", "noShowOne"); + } + } else if (command == "showLed"){ + if (param.size() == 4) { + _strip->setPixelColor( param[0].valD, _strip->Color(param[1].valD, param[2].valD, param[3].valD)); + _strip->show(); + SerialPrint("E", "Strip Ws2812b", "showLed:" + param[0].valS + " red:" + param[1].valS + " green:" + param[2].valS + " blue:" + param[3].valS); + } + } else if (command == "showLedAll"){ + if (param.size() == 3) { + for(int i=0; i<_numLeds; i++) { + _strip->setPixelColor(i, _strip->Color(param[0].valD, param[1].valD, param[2].valD)); + _strip->show(); + } + SerialPrint("E", "Strip Ws2812b", "showLedAll - red:" + param[0].valS + " green:" + param[1].valS + " blue:" + param[2].valS); + } + } else if (command == "disableIndication"){ + FlagFN = 0; + PreFlagFN = 1; + SerialPrint("E", "Strip Ws2812b", "disableIndication"); + } else if (command == "enableIndication"){ + FlagFN = 1; + PreFlagFN = 0; + doByInterval(); + SerialPrint("E", "Strip Ws2812b", "enableIndication"); + } + doByInterval(); + return {}; + } + + void setValue(const IoTValue& Value, bool genEvent = true){ + if (!_strip) return; + + value = Value; + int b = map(value.valD, 1,1024,1,255); + _strip->setBrightness(b); + _strip->show(); + regEvent(value.valD, "Ws2812b", false, genEvent); + } + + ~Ws2812b(){}; +}; + +void *getAPI_Ws2812b(String subtype, String param) +{ + if (subtype == F("Ws2812b")) { + return new Ws2812b(param); + } else { + return nullptr; + } +} + + + + diff --git a/src/modules/display/Ws2812b/modinfo.json b/src/modules/display/Ws2812b/modinfo.json new file mode 100644 index 00000000..b7565646 --- /dev/null +++ b/src/modules/display/Ws2812b/modinfo.json @@ -0,0 +1,97 @@ +{ + "menuSection": "Экраны", + "configItem": [ + { + "global": 0, + "name": "Strip ws2812b", + "type": "Reading", + "subtype": "Ws2812b", + "id": "strip", + "widget": "range", + "page": "Кнопки", + "descr": "Лента", + "int": 15, + "needSave": 0, + "pin": "4", + "numLeds": "8", + "brightness": "100", + "mode": "1", + "min": "15", + "max": "30", + "idshow": "t" + }], + + "about": { + "authorName": "Yuriy Kuneev", + "authorContact": "https://t.me/Kuneev07", + "authorGit": "", + "exampleURL": "https://iotmanager.org/wiki", + "specialThanks": "", + "moduleName": "Ws2812b", + "moduleVersion": "1.0.1", + "moduleDesc": "Позволяет визуализировать наполнение бака или температуру нагрева. В зависимост от показаний которые везуализируем нужно редактировать min и max.", + "propInfo": { + "int": "Период времени в секундах обновления.", + "pin": "Пин к которому подключена лента.", + "numLeds": "Количество пикселей в ленте.", + "needSave": "Запись яркости в энергонезависимую память", + "brightness": "Яркость ленты можно менять из сценария.", + "min": "Минимальный порог индикатора на который реагировать.", + "max": "Максимальный порог индикатора на который реагировать.", + "idshow": "id элемента конфигурации который нужно повесить индикацию." + }, + "funcInfo": [ + { + "name": "noShow", + "descr": "Выключить ленту", + "params": ["номер пикселя"] + }, + { + "name": "noShowOne", + "descr": "Выключить один светодиод на ленте", + "params": [] + }, + { + "name": "test", + "descr": "для проверки всех светодиодов ленты", + "params": [] + }, + { + "name": "showLed", + "descr": "Зажечь один диод", + "params": ["номер пикселя","цвет 255,255,255 или red,green"] + }, + { + "name": "showLedAll", + "descr": "Зажечь все диоды", + "params": ["Цвет красного светодиода от 0 до 255","Цвет зеленого светодиода от 0 до 255","Цвет синего светодиода от 0 до 255"] + }, + { + "name": "Brightness", + "descr": "Устанавливает общую яркость ленты от 0 до 255", + "params": ["яркость от 0 до 255"] + }, + { + "name": "enableIndication", + "descr": "Включает работу индикации по idshow по дэфолту включено всегда", + "params": [] + }, + { + "name": "disableIndication", + "descr": "Выключает работу индикации по idshow", + "params": [] + } + ] + }, + + "defActive": true, + + "usedLibs": { + "esp32_4mb": [ + "adafruit/Adafruit NeoPixel@^1.10.6" + ], + "esp8266_4mb": [ + "adafruit/Adafruit NeoPixel@^1.10.6" + ] + } +} \ No newline at end of file diff --git a/src/modules/exec/ButtonIn/ButtonIn.cpp b/src/modules/exec/ButtonIn/ButtonIn.cpp index 1a00f59b..aecaf87e 100644 --- a/src/modules/exec/ButtonIn/ButtonIn.cpp +++ b/src/modules/exec/ButtonIn/ButtonIn.cpp @@ -12,7 +12,7 @@ class ButtonIn : public IoTItem { String _pinMode; int _lastButtonState = LOW; unsigned long _lastDebounceTime = 0; - unsigned long _debounceDelay = 50; + long _debounceDelay = 50; int _buttonState; int _reading; @@ -23,18 +23,16 @@ class ButtonIn : public IoTItem { jsonRead(parameters, "pinMode", _pinMode); jsonRead(parameters, "debounceDelay", _debounceDelay); jsonRead(parameters, "fixState", _fixState); + _round = 0; //Serial.printf("vvvvvvvvvvvvvvvv =%d \n", _fixState); IoTgpio.pinMode(_pin, INPUT); if (_pinMode == "INPUT_PULLUP") IoTgpio.digitalWrite(_pin, HIGH); else if (_pinMode == "INPUT_PULLDOWN") IoTgpio.digitalWrite(_pin, LOW); - - // TODO: загрузить значение из памяти иначе пока просто считываем значение текущего состояния PIN value.valD = _buttonState = IoTgpio.digitalRead(_pin); // сообщаем всем о стартовом статусе без генерации события - publishStatusMqtt(_id, (String)_buttonState); - publishStatusWs(_id, (String)_buttonState); + regEvent(_buttonState, "", false, false); } void loop() { @@ -68,9 +66,9 @@ class ButtonIn : public IoTItem { _lastButtonState = _reading; } - void setValue(IoTValue Value) { + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; - regEvent((String)(int)value.valD, "ButtonIn"); + regEvent((String)(int)value.valD, "ButtonIn", false, genEvent); } String getValue() { diff --git a/src/modules/exec/ButtonIn/modinfo.json b/src/modules/exec/ButtonIn/modinfo.json index d3b91c71..df7c6a63 100644 --- a/src/modules/exec/ButtonIn/modinfo.json +++ b/src/modules/exec/ButtonIn/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [ { + "global": 0, "name": "Кнопка подключенная к пину", "type": "Writing", "subtype": "ButtonIn", @@ -9,6 +10,7 @@ "widget": "toggle", "page": "Кнопки", "descr": "Освещение", + "needSave": 0, "int": 0, "pin": 16, "execLevel": "1", @@ -42,6 +44,8 @@ "defActive": true, "usedLibs": { "esp32_4mb": [], - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } \ No newline at end of file diff --git a/src/modules/exec/ButtonOut/ButtonOut.cpp b/src/modules/exec/ButtonOut/ButtonOut.cpp index 6587275f..e5f86172 100644 --- a/src/modules/exec/ButtonOut/ButtonOut.cpp +++ b/src/modules/exec/ButtonOut/ButtonOut.cpp @@ -12,11 +12,10 @@ class ButtonOut : public IoTItem { ButtonOut(String parameters): IoTItem(parameters) { jsonRead(parameters, "pin", _pin); jsonRead(parameters, "inv", _inv); + _round = 0; IoTgpio.pinMode(_pin, OUTPUT); - //TODO: прочитать состояние из памяти - IoTgpio.digitalWrite(_pin, _inv?HIGH:LOW); // пока нет памяти, устанавливаем значение в ноль - value.valD = 0; + IoTgpio.digitalWrite(_pin, _inv?!value.valD:value.valD); } void doByInterval() { @@ -39,10 +38,10 @@ class ButtonOut : public IoTItem { return {}; // команда поддерживает возвращаемое значения. Т.е. по итогу выполнения команды или общения с внешней системой, можно вернуть значение в сценарий для дальнейшей обработки } - void setValue(IoTValue Value) { + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; IoTgpio.digitalWrite(_pin, _inv?!value.valD:value.valD); - regEvent((String)(int)value.valD, "ButtonOut"); + regEvent((String)(int)value.valD, "ButtonOut", false, genEvent); } String getValue() { diff --git a/src/modules/exec/ButtonOut/modinfo.json b/src/modules/exec/ButtonOut/modinfo.json index 41f854b8..c47c3bf5 100644 --- a/src/modules/exec/ButtonOut/modinfo.json +++ b/src/modules/exec/ButtonOut/modinfo.json @@ -2,9 +2,11 @@ "menuSection": "Исполнительные устройства", "configItem": [ { + "global": 0, "name": "Управление пином", "type": "Writing", "subtype": "ButtonOut", + "needSave": 0, "id": "btn", "widget": "toggle", "page": "Кнопки", @@ -43,6 +45,8 @@ "defActive": true, "usedLibs": { "esp32_4mb": [], - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } \ No newline at end of file diff --git a/src/modules/exec/EspCam/modinfo.json b/src/modules/exec/EspCam/modinfo.json index 1cb8f2d9..ec35a2a2 100644 --- a/src/modules/exec/EspCam/modinfo.json +++ b/src/modules/exec/EspCam/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "Camera OV2640 (ESPcam)", "type": "Reading", "subtype": "EspCam", diff --git a/src/modules/exec/IoTServo/IoTServo.cpp b/src/modules/exec/IoTServo/IoTServo.cpp index 8c7cc357..3cd672cf 100644 --- a/src/modules/exec/IoTServo/IoTServo.cpp +++ b/src/modules/exec/IoTServo/IoTServo.cpp @@ -52,12 +52,12 @@ class IoTServo : public IoTItem { return {}; } - void setValue(IoTValue Value) { + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; if (value.isDecimal & (_oldValue != value.valD)) { _oldValue = value.valD; servObj.write(_oldValue); - regEvent(value.valD, "IoTServo"); + regEvent(value.valD, "IoTServo", false, genEvent); } } diff --git a/src/modules/exec/IoTServo/modinfo.json b/src/modules/exec/IoTServo/modinfo.json index 89c28a02..b9a16e1b 100644 --- a/src/modules/exec/IoTServo/modinfo.json +++ b/src/modules/exec/IoTServo/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "Сервопривод", "type": "Writing", "subtype": "IoTServo", diff --git a/src/modules/exec/Mcp23017/Mcp23017.cpp b/src/modules/exec/Mcp23017/Mcp23017.cpp index 657a3fde..b5fc7f06 100644 --- a/src/modules/exec/Mcp23017/Mcp23017.cpp +++ b/src/modules/exec/Mcp23017/Mcp23017.cpp @@ -3,8 +3,6 @@ #include "classes/IoTGpio.h" #include -void scanI2C(); - class Mcp23017Driver : public IoTGpio { private: Adafruit_MCP23X17 _mcp; @@ -39,14 +37,14 @@ class Mcp23017Driver : public IoTGpio { class Mcp23017 : public IoTItem { private: Mcp23017Driver* _driver; + String _addr; public: Mcp23017(String parameters) : IoTItem(parameters) { _driver = nullptr; - String addr; - jsonRead(parameters, "addr", addr); - if (addr == "") { + jsonRead(parameters, "addr", _addr); + if (_addr == "") { scanI2C(); return; } @@ -58,10 +56,15 @@ class Mcp23017 : public IoTItem { return; } - _driver = new Mcp23017Driver(index, addr); + _driver = new Mcp23017Driver(index, _addr); } - void doByInterval() {} + void doByInterval() { + if (_addr == "") { + scanI2C(); + return; + } + } //возвращает ссылку на экземпляр класса Mcp23017Driver IoTGpio* getGpioDriver() { diff --git a/src/modules/exec/Mcp23017/modinfo.json b/src/modules/exec/Mcp23017/modinfo.json index ca836d72..3c63dad8 100644 --- a/src/modules/exec/Mcp23017/modinfo.json +++ b/src/modules/exec/Mcp23017/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "Расширитель портов Mcp23017", "type": "Reading", "subtype": "Mcp23017", @@ -45,6 +46,14 @@ "esp8266_4mb": [ "adafruit/Adafruit MCP23017 Arduino Library@^2.1.0", "adafruit/Adafruit BusIO @ ^1.13.2" + ], + "esp8266_1mb": [ + "adafruit/Adafruit MCP23017 Arduino Library@^2.1.0", + "adafruit/Adafruit BusIO @ ^1.13.2" + ], + "esp8266_1mb_ota": [ + "adafruit/Adafruit MCP23017 Arduino Library@^2.1.0", + "adafruit/Adafruit BusIO @ ^1.13.2" ] } } \ No newline at end of file diff --git a/src/modules/exec/Mp3/modinfo.json b/src/modules/exec/Mp3/modinfo.json index 7b9dd98b..efd4f6f8 100644 --- a/src/modules/exec/Mp3/modinfo.json +++ b/src/modules/exec/Mp3/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "MP3 плеер", "type": "Reading", "subtype": "Mp3", diff --git a/src/modules/exec/Multitouch/Multitouch.cpp b/src/modules/exec/Multitouch/Multitouch.cpp new file mode 100644 index 00000000..9e6b30ba --- /dev/null +++ b/src/modules/exec/Multitouch/Multitouch.cpp @@ -0,0 +1,94 @@ +#include "Global.h" +#include "classes/IoTItem.h" + +extern IoTGpio IoTgpio; + +class Multitouch : public IoTItem +{ +private: + int _pin; + int _int; + int _inv; + String _pinMode; + int _lastButtonState = LOW; + unsigned long _lastDebounceTime = 0; + unsigned long timing; + long _debounceDelay = 50; + long _PWMDelay = 500; + int _buttonState; + int _reading; + int _count = 0; + int duration = 0; + +public: + Multitouch(String parameters) : IoTItem(parameters) + { + jsonRead(parameters, "pin", _pin); + jsonRead(parameters, "pinMode", _pinMode); + jsonRead(parameters, "debounceDelay", _debounceDelay); + jsonRead(parameters, "PWMDelay", _PWMDelay); + jsonRead(parameters, "int", _int); + jsonRead(parameters, "inv", _inv); + _round = 0; + + IoTgpio.pinMode(_pin, INPUT); + if (_pinMode == "INPUT_PULLUP") + IoTgpio.digitalWrite(_pin, HIGH); + else if (_pinMode == "INPUT_PULLDOWN") + IoTgpio.digitalWrite(_pin, LOW); + + value.valD = _buttonState = IoTgpio.digitalRead(_pin); + // сообщаем всем о стартовом статусе без генерации события + regEvent(_buttonState, "", false, false); + } + + void loop() + { + _reading = IoTgpio.digitalRead(_pin); + if (_reading != _lastButtonState) + { + _lastDebounceTime = millis(); + } + + if ((millis() - _lastDebounceTime) > _debounceDelay) + { + if (millis() - timing > _int && _reading == _inv && millis() - _lastDebounceTime > _PWMDelay) + { + timing = millis(); + duration = millis() - _lastDebounceTime - _PWMDelay; + value.valD = duration / 50; + regEvent(value.valD, "Multitouch"); + _count = -1; + } + + if (_reading != _buttonState) + { + _buttonState = _reading; + _count++; + duration = 0; + } + + if (1 < _count && millis() > _lastDebounceTime + _PWMDelay) + { + value.valD = _count / 2; + regEvent(value.valD, "Multitouch"); + _count = 0; + } + } + _lastButtonState = _reading; + } + + ~Multitouch(){}; +}; + +void *getAPI_Multitouch(String subtype, String param) +{ + if (subtype == F("Multitouch")) + { + return new Multitouch(param); + } + else + { + return nullptr; + } +} \ No newline at end of file diff --git a/src/modules/exec/Multitouch/modinfo.json b/src/modules/exec/Multitouch/modinfo.json new file mode 100644 index 00000000..26d5c6ca --- /dev/null +++ b/src/modules/exec/Multitouch/modinfo.json @@ -0,0 +1,51 @@ +{ + "menuSection": "Исполнительные устройства", + "configItem": [ + { + "global": 0, + "name": "Сенсорная кнопка", + "type": "Writing", + "subtype": "Multitouch", + "id": "impulse", + "widget": "anydataDef", + "page": "Кнопки", + "descr": "Количество нажаний", + "needSave": 0, + "int": 300, + "inv": 1, + "pin": 16, + "pinMode": "INPUT", + "debounceDelay": 50, + "PWMDelay": 500 + } + ], + "about": { + "authorName": "AVAKS", + "authorContact": "https://t.me/@avaks_dev", + "authorGit": "https://github.com/avaksru", + "specialThanks": "", + "moduleName": "Multitouch", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Модуль чтения состояния GPIO (pin)", + "moduleDesc": "Считает количество нажатий на выключатель без фиксации или сенсорную кнопку. При удержании кнопки нажатой - считает длительность нажатия. Позволяет реализовать логику работы: включения различных устройств в зависимости от количества нажатий, диммировать яркость удержанием выключателя нажатым, а так же счетчик импульсов, дверной звонок, сенсорный выключатель, концевой выключатель, датчик открытия окна, и т.п.", + "propInfo": { + "int": "Интервал отправки времени удержания кнопки (миллисекунд)", + "pin": "Укажите GPIO номер пина для чтения состояний подключенной кнопки", + "inv": "Инверсия GPIO", + "pinMode": "Может быть INPUT_PULLUP INPUT_PULLDOWN INPUT", + "debounceDelay": "Время обработки дребезга (миллисекунд)", + "PWMDelay": "Время ожидания повторного нажатия. И время после которого начитается отсчет длительности непрерывного ражатия (миллисекунд)" + } + }, + "defActive": true, + "usedLibs": { + "esp32_4mb": [], + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] + } +} diff --git a/src/modules/exec/Pcf8574/Pcf8574.cpp b/src/modules/exec/Pcf8574/Pcf8574.cpp index d5403752..962feea5 100644 --- a/src/modules/exec/Pcf8574/Pcf8574.cpp +++ b/src/modules/exec/Pcf8574/Pcf8574.cpp @@ -93,14 +93,14 @@ class Pcf8574Driver : public IoTGpio { class Pcf8574 : public IoTItem { private: Pcf8574Driver* _driver; + String _addr; public: Pcf8574(String parameters) : IoTItem(parameters) { _driver = nullptr; - String addr; - jsonRead(parameters, "addr", addr); - if (addr == "") { + jsonRead(parameters, "addr", _addr); + if (_addr == "") { scanI2C(); return; } @@ -112,10 +112,15 @@ class Pcf8574 : public IoTItem { return; } - _driver = new Pcf8574Driver(index, addr); + _driver = new Pcf8574Driver(index, _addr); } - void doByInterval() {} + void doByInterval() { + if (_addr == "") { + scanI2C(); + return; + } + } //возвращает ссылку на экземпляр класса Pcf8574Driver IoTGpio* getGpioDriver() { diff --git a/src/modules/exec/Pcf8574/modinfo.json b/src/modules/exec/Pcf8574/modinfo.json index 820923d6..7f1f3817 100644 --- a/src/modules/exec/Pcf8574/modinfo.json +++ b/src/modules/exec/Pcf8574/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "Расширитель портов Pcf8574", "type": "Reading", "subtype": "Pcf8574", @@ -37,6 +38,12 @@ ], "esp8266_4mb": [ "adafruit/Adafruit BusIO @ ^1.13.2" + ], + "esp8266_1mb": [ + "adafruit/Adafruit BusIO @ ^1.13.2" + ], + "esp8266_1mb_ota": [ + "adafruit/Adafruit BusIO @ ^1.13.2" ] } } \ No newline at end of file diff --git a/src/modules/exec/Pwm32/Pwm32.cpp b/src/modules/exec/Pwm32/Pwm32.cpp index 6798da3f..79f6eedf 100644 --- a/src/modules/exec/Pwm32/Pwm32.cpp +++ b/src/modules/exec/Pwm32/Pwm32.cpp @@ -54,10 +54,10 @@ class Pwm32 : public IoTItem { } } - void setValue(IoTValue Value) { + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; ledcWrite(_ledChannel, value.valD); - regEvent(value.valD, "Pwm32"); + regEvent(value.valD, "Pwm32", false, genEvent); } //======================================================================================================= diff --git a/src/modules/exec/Pwm32/modinfo.json b/src/modules/exec/Pwm32/modinfo.json index 37a60ff8..dda8786b 100644 --- a/src/modules/exec/Pwm32/modinfo.json +++ b/src/modules/exec/Pwm32/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "PWM ESP32", "type": "Writing", "subtype": "Pwm32", diff --git a/src/modules/exec/Pwm8266/Pwm8266.cpp b/src/modules/exec/Pwm8266/Pwm8266.cpp index 54307881..7e61a80a 100644 --- a/src/modules/exec/Pwm8266/Pwm8266.cpp +++ b/src/modules/exec/Pwm8266/Pwm8266.cpp @@ -20,6 +20,7 @@ class Pwm8266 : public IoTItem { IoTgpio.pinMode(_pin, OUTPUT); analogWriteFreq(_freq); + analogWriteResolution(10); IoTgpio.analogWrite(_pin, value.valD); jsonRead(parameters, "apin", _apin); @@ -44,10 +45,10 @@ class Pwm8266 : public IoTItem { } } - void setValue(IoTValue Value) { + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; IoTgpio.analogWrite(_pin, value.valD); - regEvent(value.valD, "Pwm8266"); + regEvent(value.valD, "Pwm8266", false, genEvent); } //======================================================================================================= diff --git a/src/modules/exec/Pwm8266/modinfo.json b/src/modules/exec/Pwm8266/modinfo.json index 806e6028..494a2fbf 100644 --- a/src/modules/exec/Pwm8266/modinfo.json +++ b/src/modules/exec/Pwm8266/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "PWM ESP8266", "type": "Writing", "subtype": "Pwm8266", @@ -41,6 +42,8 @@ "defActive": true, "usedLibs": { - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } \ No newline at end of file diff --git a/src/modules/exec/SDcard/modinfo.json b/src/modules/exec/SDcard/modinfo.json index 4f6b4867..ef917963 100644 --- a/src/modules/exec/SDcard/modinfo.json +++ b/src/modules/exec/SDcard/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "SD карта", "type": "Writing", "subtype": "SDcard", diff --git a/src/modules/exec/SysExt/modinfo.json b/src/modules/exec/SysExt/modinfo.json index 72d394c3..3f4313dc 100644 --- a/src/modules/exec/SysExt/modinfo.json +++ b/src/modules/exec/SysExt/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "Доп. функции системы", "type": "Reading", "subtype": "SysExt", diff --git a/src/modules/exec/Telegram/modinfo.json b/src/modules/exec/Telegram/modinfo.json index 9104bb52..b28a2478 100644 --- a/src/modules/exec/Telegram/modinfo.json +++ b/src/modules/exec/Telegram/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Исполнительные устройства", "configItem": [{ + "global": 0, "name": "Телеграм-Бот", "type": "Writing", "subtype": "Telegram", diff --git a/src/modules/exec/TelegramLT/TelegramLT.cpp b/src/modules/exec/TelegramLT/TelegramLT.cpp index f57ba04b..3c177e7a 100644 --- a/src/modules/exec/TelegramLT/TelegramLT.cpp +++ b/src/modules/exec/TelegramLT/TelegramLT.cpp @@ -7,32 +7,38 @@ class TelegramLT : public IoTItem public: String _prevMsg = ""; String _token; - unsigned long _chatID; + String _chatID; - TelegramLT(String parameters) : IoTItem(parameters) { + TelegramLT(String parameters) : IoTItem(parameters) + { jsonRead(parameters, "token", _token); jsonRead(parameters, "chatID", _chatID); } void sendTelegramMsg(bool often, String msg) - { - if (WiFi.status() == WL_CONNECTED && (often || !often && _prevMsg != msg)) { + { + if (WiFi.status() == WL_CONNECTED && (often || !often && _prevMsg != msg)) + { WiFiClient client; HTTPClient http; http.begin(client, "http://live-control.com/iotm/telegram.php"); http.addHeader("Content-Type", "application/x-www-form-urlencoded"); - String httpRequestData = "url=https://api.telegram.org/bot" + _token + "/sendmessage?chat_id=" + uint64ToString(_chatID) + "&text=" + msg; + String httpRequestData = "url=https://api.telegram.org/bot" + _token + "/sendmessage?chat_id=" + _chatID + "&text=" + msg; int httpResponseCode = http.POST(httpRequestData); String payload = http.getString(); - SerialPrint("<-", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", msg: " + msg); - SerialPrint("->", F("Telegram"), "chat ID: " + uint64ToString(_chatID) + ", server: " + httpResponseCode); + SerialPrint("<-", F("Telegram"), "chat ID: " + _chatID + ", msg: " + msg); + SerialPrint("->", F("Telegram"), "chat ID: " + _chatID + ", server: " + httpResponseCode); - if (!strstr(payload.c_str(), "{\"ok\":true")) { - value.valD = 1; + if (!strstr(payload.c_str(), "{\"ok\":true")) + { + value.valD = 0; Serial.printf("Telegram error, msg from server: %s\n", payload.c_str()); regEvent(value.valD, payload); - } else { - value.valD = 0; + } + else + { + value.valD = 1; + regEvent(value.valD, payload); } http.end(); _prevMsg = msg; @@ -41,7 +47,8 @@ public: IoTValue execute(String command, std::vector ¶m) { - if (param.size() == 1) { + if (param.size() == 1) + { String strTmp; if (param[0].isDecimal && param[0].valS == "") strTmp = param[0].valD; diff --git a/src/modules/exec/TelegramLT/modinfo.json b/src/modules/exec/TelegramLT/modinfo.json index 598441f4..28bcade9 100644 --- a/src/modules/exec/TelegramLT/modinfo.json +++ b/src/modules/exec/TelegramLT/modinfo.json @@ -3,6 +3,7 @@ "configItem": [ { + "global": 0, "name": "Телеграм-Лайт", "type": "Writing", "subtype": "TelegramLT", @@ -21,11 +22,11 @@ "authorGit": "https://github.com/avaksru", "specialThanks": "", "moduleName": "TelegramLT", - "moduleVersion": "1.0", + "moduleVersion": "2", "usedRam": { - "esp32_4mb": 15, - "esp8266_4mb": 15 - }, + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, "title": "Телеграм-извещатель", "moduleDesc": "Только отправка уведомлений в телеграм о событиях. Модуль занимает значительно меньше памяти в ESP по сравнению со стандартным. Внимание! для отправки сообщений используется промежуточный сервер http://live-control.com", "propInfo": { @@ -34,15 +35,19 @@ }, "retInfo": "Элемент данного модуля может иметь два значения 0 - все хорошо, 1 - произошла ошибка отправки сообщения, подробности в консоли. Данный статус можно использовать в сценарии для совершения экстренных действий при ошибке.", "funcInfo": [ - { - "name": "sendMsg", - "descr": "Отправить сообщение без повторений.", - "params": ["Сообщение, может быть строкой, числом или ИД другого элемента для получения значения"] + { + "name": "sendMsg", + "descr": "Отправить сообщение без повторений.", + "params": [ + "Сообщение, может быть строкой, числом или ИД другого элемента для получения значения" + ] }, - { - "name": "sendOftenMsg", - "descr": "Отправить сообщение в любом случае, даж если отправляли такое ранее.", - "params": ["Сообщение, может быть строкой, числом или ИД другого элемента для получения значения"] + { + "name": "sendOftenMsg", + "descr": "Отправить сообщение в любом случае, даж если отправляли такое ранее.", + "params": [ + "Сообщение, может быть строкой, числом или ИД другого элемента для получения значения" + ] } ] }, @@ -51,6 +56,8 @@ "usedLibs": { "esp32_4mb": [], - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } diff --git a/src/modules/sceninfo.json b/src/modules/sceninfo.json index b358033e..fb472e38 100644 --- a/src/modules/sceninfo.json +++ b/src/modules/sceninfo.json @@ -9,6 +9,11 @@ "title": "Сценарии", "moduleDesc": "Сценарии позволяют реализовать индивидуальный алгоритм работы контроллера с учетом происходящих событий. Они представляют из себя описательный язык того, что нужно сделать при наступлении того или иного события, учитывая конкретные условия. \nВ базе языка - выражение вида: “Если условие истина, то выполнить одно действие, а если нет, то иное”. При этом проверка такого выражения будет осуществляться только при наступлении события связанного с элементом конфигурации, который упоминается в этом выражении. \nУсловием или действием может быть любое разрешенное выражение. Они все при выполнении возвращают значение. Выражение может состоять из: идентификаторов элементов конфигурации, чисел (целые, дробные и отрицательные), строк в кавычках, операций сравнения < > <= >= == !=, операций присваивания значений =, математических операций +-*/, логических операций &|, комментариев после символа #, функций (в параметрах которых так же могут быть любые разрешенные выражения), конструкции ветвления IfThenElse, группирующие блоки выражений {}", "funcInfo": [ + { + "name": "getIntFromNet", + "descr": "Получаем количество секунд доверия к значениям элемента. При -2 доверие полное, при -1 время доверия истекло. При >0 время обратного отсчета. Используется только совместно с ИД элемента: ID.getIntFromNet()", + "params": [] + }, { "name": "exit", "descr": "Прерываем работу сценария и выводим в консоль причину. Причина не обязательна.", @@ -44,6 +49,11 @@ "descr": "Погрузить ESP в глубокий сон. Вывод из сна с перезагрузкой. Для ESP8266 необходимо соединить gpio 16 и RST", "params": ["ЧислоСекунд"] }, + { + "name": "getRSSI", + "descr": "Получить величину уровня принимаемого сигнала WI-FI.", + "params": [] + }, { "name": "getIP", "descr": "Получить строку IP ESP", diff --git a/src/modules/sensors/Ads1115/Ads1115.cpp b/src/modules/sensors/Ads1115/Ads1115.cpp index 9ac3b98b..b043d8fe 100644 --- a/src/modules/sensors/Ads1115/Ads1115.cpp +++ b/src/modules/sensors/Ads1115/Ads1115.cpp @@ -12,13 +12,12 @@ #include "Wire.h" #include // Библиотека для работы с модулями ADS1115 и ADS1015 -// to do убрать глобальный экземпляр -Adafruit_ADS1115 ads; class Ads1115 : public IoTItem { int _pin; bool _isRaw; bool _isInited = false; + Adafruit_ADS1115 ads; public: Ads1115(String parameters) : IoTItem(parameters) { diff --git a/src/modules/sensors/Ads1115/modinfo.json b/src/modules/sensors/Ads1115/modinfo.json index bf406e26..6395fbff 100644 --- a/src/modules/sensors/Ads1115/modinfo.json +++ b/src/modules/sensors/Ads1115/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "ADS1115 Напряжение", "type": "Reading", "subtype": "Ads1115", diff --git a/src/modules/sensors/AhtXX/AhtXX.cpp b/src/modules/sensors/AhtXX/AhtXX.cpp index f32487bf..a49e8fe1 100644 --- a/src/modules/sensors/AhtXX/AhtXX.cpp +++ b/src/modules/sensors/AhtXX/AhtXX.cpp @@ -7,30 +7,30 @@ std::map ahts; -void printStatus(AHTxx *aht) { +String getStatus(AHTxx *aht) { switch (aht->getStatus()) { case AHTXX_NO_ERROR: - Serial.println(F("no error")); + return F("no error"); break; case AHTXX_BUSY_ERROR: - Serial.println(F("sensor AHT busy, increase polling time")); + return F("sensor AHT busy, increase polling time"); break; case AHTXX_ACK_ERROR: - Serial.println(F("sensor AHT didn't return ACK, not connected, broken, long wires (reduce speed), bus locked by slave (increase stretch limit)")); + return F("sensor AHT didn't return ACK, not connected, broken, long wires (reduce speed), bus locked by slave (increase stretch limit)"); break; case AHTXX_DATA_ERROR: - Serial.println(F(" AHT: received data smaller than expected, not connected, broken, long wires (reduce speed), bus locked by slave (increase stretch limit)")); + return F(" AHT: received data smaller than expected, not connected, broken, long wires (reduce speed), bus locked by slave (increase stretch limit)"); break; case AHTXX_CRC8_ERROR: - Serial.println(F("AHT: computed CRC8 not match received CRC8, this feature supported only by AHT2x sensors")); + return F("AHT: computed CRC8 not match received CRC8, this feature supported only by AHT2x sensors"); break; default: - Serial.println(F("AHT: unknown status")); + return F("AHT: unknown status"); break; } } @@ -49,7 +49,7 @@ class AhtXXt : public IoTItem { if (value.valD != AHTXX_ERROR) { regEvent(value.valD, "AhtXXt"); } else { - printStatus(_aht); //print temperature command status + SerialPrint("E", "Sensor AHTXX", getStatus(_aht), _id); } } @@ -70,7 +70,7 @@ class AhtXXh : public IoTItem { if (value.valD != AHTXX_ERROR) { regEvent(value.valD, "AhtXXh"); } else { - printStatus(_aht); //print temperature command status + SerialPrint("E", "Sensor AHTXX", getStatus(_aht), _id); } } diff --git a/src/modules/sensors/AhtXX/modinfo.json b/src/modules/sensors/AhtXX/modinfo.json index 87527032..7b180bec 100644 --- a/src/modules/sensors/AhtXX/modinfo.json +++ b/src/modules/sensors/AhtXX/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "AHTXX Температура", "type": "Reading", "subtype": "AhtXXt", @@ -15,6 +16,7 @@ "round": 1 }, { + "global": 0, "name": "AHTXX Влажность", "type": "Reading", "subtype": "AhtXXh", diff --git a/src/modules/sensors/AnalogAdc/modinfo.json b/src/modules/sensors/AnalogAdc/modinfo.json index 55ab6360..31b99f0a 100644 --- a/src/modules/sensors/AnalogAdc/modinfo.json +++ b/src/modules/sensors/AnalogAdc/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [{ + "global": 0, "name": "Аналоговый сенсор", "type": "Reading", "subtype": "AnalogAdc", diff --git a/src/modules/sensors/Ble/Ble.cpp b/src/modules/sensors/Ble/Ble.cpp new file mode 100644 index 00000000..8632fd97 --- /dev/null +++ b/src/modules/sensors/Ble/Ble.cpp @@ -0,0 +1,248 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include +#ifdef ESP32 +#include +#include + +// Создаем переменную для хранения данных с датчиков bluetooth +StaticJsonDocument BLEbuffer; +JsonObject extBLEdata = BLEbuffer.to(); + +BLEScan *pBLEScan; +TheengsDecoder decoder; +StaticJsonDocument<512> doc; + +class BleScan : public IoTItem, BLEAdvertisedDeviceCallbacks +{ +private: + //описание параметров передаваемых из настроек датчика из веба + int _scanDuration; + String _filter; + +public: + //======================================================================================================= + std::string convertServiceData(std::string deviceServiceData) + { + int serviceDataLength = (int)deviceServiceData.length(); + char spr[2 * serviceDataLength + 1]; + for (int i = 0; i < serviceDataLength; i++) + sprintf(spr + 2 * i, "%.2x", (unsigned char)deviceServiceData[i]); + spr[2 * serviceDataLength] = 0; + return spr; + } + + void onResult(BLEAdvertisedDevice *advertisedDevice) + { + JsonObject BLEdata = doc.to(); + String mac_adress_ = advertisedDevice->getAddress().toString().c_str(); + mac_adress_.toUpperCase(); + BLEdata["id"] = (char *)mac_adress_.c_str(); + + if (advertisedDevice->haveName()) + { + BLEdata["name"] = (char *)advertisedDevice->getName().c_str(); + } + if (advertisedDevice->haveManufacturerData()) + { + char *manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t *)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); + BLEdata["manufacturerdata"] = manufacturerdata; + free(manufacturerdata); + } + if (advertisedDevice->haveRSSI()) + BLEdata["rssi"] = (int)advertisedDevice->getRSSI(); + if (advertisedDevice->haveTXPower()) + BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower(); + if (advertisedDevice->haveServiceData()) + { + int serviceDataCount = advertisedDevice->getServiceDataCount(); + for (int j = 0; j < serviceDataCount; j++) + { + std::string service_data = convertServiceData(advertisedDevice->getServiceData(j)); + BLEdata["servicedata"] = (char *)service_data.c_str(); + std::string serviceDatauuid = advertisedDevice->getServiceDataUUID(j).toString(); + BLEdata["servicedatauuid"] = (char *)serviceDatauuid.c_str(); + } + } + + if (decoder.decodeBLEJson(BLEdata)) + { + + BLEdata.remove("manufacturerdata"); + BLEdata.remove("servicedata"); + + String mac_address = BLEdata["id"].as(); + mac_address.replace(":", ""); + + if (_filter != "") + { + if (BLEdata[_filter]) + { + for (JsonPair kv : BLEdata) + { + extBLEdata[mac_address][kv.key()] = BLEdata[kv.key()]; + } + + // дописываем время прихода пакета данных + extBLEdata[mac_address]["last"] = millis(); + } + } + else + { + for (JsonPair kv : BLEdata) + { + extBLEdata[mac_address][kv.key()] = BLEdata[kv.key()]; + } + // дописываем время прихода пакета данных + extBLEdata[mac_address]["last"] = millis(); + } + }; + } + + BleScan(String parameters) : IoTItem(parameters) + { + _scanDuration = jsonReadInt(parameters, "scanDuration"); + _filter = jsonReadStr(parameters, "filter"); + + if (pBLEScan->isScanning() == false) + { + SerialPrint("i", F("BLE"), "Start Scanning..."); + BLEDevice::init(""); + pBLEScan = BLEDevice::getScan(); // create new scan + pBLEScan->setAdvertisedDeviceCallbacks(this); + pBLEScan->setActiveScan(false); // active scan uses more power, but get results faster + pBLEScan->setInterval(100); + pBLEScan->setWindow(99); // less or equal setInterval value + } + } + + //======================================================================================================= + + // doByInterval() + void doByInterval() + { + + if (_scanDuration > 0) + { + BLEScanResults foundDevices = pBLEScan->start(_scanDuration, true); + int count = foundDevices.getCount(); + SerialPrint("i", F("BLE"), "Devices found: " + String(count)); + SerialPrint("i", F("BLE"), "Scan done!"); + pBLEScan->clearResults(); + } + for (JsonPair kv : extBLEdata) + { + String val = extBLEdata[kv.key()].as(); + SerialPrint("i", F("BLE"), _id + " " + kv.key().c_str() + " " + val); + } + } + + //======================================================================================================= + + ~BleScan(){}; +}; + +class BleSens : public IoTItem +{ +private: + //описание параметров передаваемых из настроек датчика из веба + String _MAC; + String _sensor; + +public: + //======================================================================================================= + char *TimeToString(unsigned long t) + { + static char str[12]; + long h = t / 3600; + t = t % 3600; + int m = t / 60; + int s = t % 60; + sprintf(str, "%02ld:%02d:%02d", h, m, s); + return str; + } + + BleSens(String parameters) : IoTItem(parameters) + { + _MAC = jsonReadStr(parameters, "MAC"); + _sensor = jsonReadStr(parameters, "sensor"); + } + + //======================================================================================================= + + // doByInterval() + void doByInterval() + { + if (_sensor == "last") + { + int valInt = extBLEdata[_MAC][_sensor].as(); + char *s; + s = TimeToString(millis() / 1000 - valInt / 1000); + value.isDecimal = 0; + if (valInt > 0) + { + value.valS = s; + } + else + { + value.valS = ""; + } + regEvent(value.valS, _id); + } + else + { + String valStr = extBLEdata[_MAC][_sensor].as(); + if (valStr != "null") + { + if (value.isDecimal = isDigitDotCommaStr(valStr)) + { + value.isDecimal = 1; + value.valD = valStr.toFloat(); + regEvent(value.valD, _id); + } + else + { + value.isDecimal = 0; + value.valS = valStr; + regEvent(value.valS, _id); + } + } + else + { + value.isDecimal = 0; + value.valS = ""; + regEvent(value.valS, _id); + } + } + } + //======================================================================================================= + + ~BleSens(){}; +}; +#endif + +// Заглушка для ESP8266 +#ifdef ESP8266 +class Ble : public IoTItem +{ +private: +public: + Ble(String parameters) : IoTItem(parameters) {} +}; +#endif + +void *getAPI_Ble(String subtype, String param) +{ + if (subtype == F("BleScan")) + { + return new BleScan(param); + } + else if (subtype == F("BleSens")) + { + return new BleSens(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/sensors/Ble/modinfo.json b/src/modules/sensors/Ble/modinfo.json new file mode 100644 index 00000000..fda2ea7a --- /dev/null +++ b/src/modules/sensors/Ble/modinfo.json @@ -0,0 +1,64 @@ +{ + "menuSection": "Сенсоры", + "configItem": [ + { + "name": "bluetooth сканер", + "num": 1, + "type": "Reading", + "subtype": "BleScan", + "id": "BleScan", + "widget": "na", + "page": "", + "descr": "", + "int": 135, + "scanDuration": 10, + "filter": "servicedatauuid" + }, + { + "name": "bluetooth датчик", + "num": 1, + "type": "Reading", + "subtype": "BleSens", + "id": "BleSens", + "widget": "anydataDef", + "page": "Сенсоры", + "descr": "", + "needSave": 0, + "global": 0, + "round": 1, + "int": 60, + "MAC": "", + "sensor": "" + } + ], + "about": { + "authorName": "AVAKS", + "authorContact": "https://t.me/@avaks_dev", + "authorGit": "https://github.com/avaksru", + "specialThanks": "@Serghei63", + "moduleName": "Ble", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 1261449, + "esp8266_4mb": 0 + }, + "subTypes": ["BleSens", "BleScan"], + "title": "Сканер Bluetooth", + "moduleDesc": "Позволяет получить данные с Bluetooth часов и термометров Mijia, Xiaomi, Cleargrass, ...", + "propInfo": { + "round": "Округление после запятой.", + "int": "Интервал сканирования BLE окружения (BleScan) / Интервал отправки собранной телеметрии в MQTT (BleSens)", + "scanDuration": "Длительность сканирования ", + "filter": "Позволяет установить фильтр по параметру передаваемому датчиком. Данные будут считываться только с датчиков у которых есть передаваемый параметр указанный в фильтре", + "MAC": "MAC адрес беспроводного датчика", + "sensor": "Тип сенсора: температура / влажность / время / ... " + } + }, + "defActive": false, + "usedLibs": { + "esp32_4mb": [ + "https://github.com/h2zero/NimBLE-Arduino.git", + "https://github.com/avaksru/decoder.git" + ] + } +} diff --git a/src/modules/sensors/Bme280/Bme280.cpp b/src/modules/sensors/Bme280/Bme280.cpp index aa9050c8..071525c4 100644 --- a/src/modules/sensors/Bme280/Bme280.cpp +++ b/src/modules/sensors/Bme280/Bme280.cpp @@ -26,7 +26,7 @@ class Bme280t : public IoTItem { if (value.valD != NAN && value.valD < 145) regEvent(value.valD, "Bme280t"); else - SerialPrint("E", "Sensor Bme280t", "Error"); + SerialPrint("E", "Sensor Bme280t", "Error", _id); } ~Bme280t(){}; @@ -46,7 +46,7 @@ class Bme280h : public IoTItem { if (value.valD != NAN && value.valD < 100) regEvent(value.valD, "Bme280h"); else - SerialPrint("E", "Sensor Bme280h", "Error"); + SerialPrint("E", "Sensor Bme280h", "Error", _id); } ~Bme280h(){}; @@ -67,7 +67,7 @@ class Bme280p : public IoTItem { value.valD = value.valD / 1.333224 / 100; regEvent(value.valD, "Bme280p"); } else - SerialPrint("E", "Sensor Bme280p", "Error"); + SerialPrint("E", "Sensor Bme280p", "Error", _id); } ~Bme280p(){}; diff --git a/src/modules/sensors/Bme280/modinfo.json b/src/modules/sensors/Bme280/modinfo.json index 62d8d17c..d2b05beb 100644 --- a/src/modules/sensors/Bme280/modinfo.json +++ b/src/modules/sensors/Bme280/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "BME280 Температура", "type": "Reading", "subtype": "Bme280t", @@ -14,6 +15,7 @@ "round": 1 }, { + "global": 0, "name": "BME280 Давление", "type": "Reading", "subtype": "Bme280p", @@ -26,6 +28,7 @@ "round": 1 }, { + "global": 0, "name": "BME280 Влажность", "type": "Reading", "subtype": "Bme280h", diff --git a/src/modules/sensors/Bmp280/Bmp280.cpp b/src/modules/sensors/Bmp280/Bmp280.cpp index fae4ccfa..d0db6beb 100644 --- a/src/modules/sensors/Bmp280/Bmp280.cpp +++ b/src/modules/sensors/Bmp280/Bmp280.cpp @@ -26,7 +26,7 @@ class Bmp280t : public IoTItem { if (value.valD != NAN && value.valD < 150) regEvent(value.valD, "Bmp280t"); else - SerialPrint("E", "Sensor Bmp280t", "Error"); + SerialPrint("E", "Sensor Bmp280t", "Error", _id); } ~Bmp280t(){}; @@ -47,7 +47,7 @@ class Bmp280p : public IoTItem { value.valD = value.valD / 1.333224 / 100; regEvent(value.valD, "Bmp280p"); } else - SerialPrint("E", "Sensor Bmp280p", "Error"); + SerialPrint("E", "Sensor Bmp280p", "Error", _id); } ~Bmp280p(){}; diff --git a/src/modules/sensors/Bmp280/modinfo.json b/src/modules/sensors/Bmp280/modinfo.json index 50b84a82..4bfb5c27 100644 --- a/src/modules/sensors/Bmp280/modinfo.json +++ b/src/modules/sensors/Bmp280/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "BMP280 Температура", "type": "Reading", "subtype": "Bmp280t", @@ -14,6 +15,7 @@ "round": 1 }, { + "global": 0, "name": "BMP280 Давление", "type": "Reading", "subtype": "Bmp280p", diff --git a/src/modules/sensors/Dht1122/Dht1122.cpp b/src/modules/sensors/Dht1122/Dht1122.cpp index a9c00ef4..00641724 100644 --- a/src/modules/sensors/Dht1122/Dht1122.cpp +++ b/src/modules/sensors/Dht1122/Dht1122.cpp @@ -26,7 +26,7 @@ class Dht1122t : public IoTItem { if (String(value.valD) != "nan") regEvent(value.valD, "Dht1122t"); else - SerialPrint("E", "Sensor DHTt", "Error"); + SerialPrint("E", "Sensor DHTt", "Error", _id); } ~Dht1122t(){}; @@ -46,7 +46,7 @@ class Dht1122h : public IoTItem { if (String(value.valD) != "nan") regEvent(value.valD, "Dht1122h"); else - SerialPrint("E", "Sensor DHTh", "Error"); + SerialPrint("E", "Sensor DHTh", "Error", _id); } ~Dht1122h(){}; diff --git a/src/modules/sensors/Dht1122/modinfo.json b/src/modules/sensors/Dht1122/modinfo.json index 85089458..7e5dc5be 100644 --- a/src/modules/sensors/Dht1122/modinfo.json +++ b/src/modules/sensors/Dht1122/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "DHT11 Температура", "type": "Reading", "subtype": "Dht1122t", @@ -14,6 +15,7 @@ "senstype": "dht11" }, { + "global": 0, "name": "DHT11 Влажность", "type": "Reading", "subtype": "Dht1122h", diff --git a/src/modules/sensors/Ds18b20/Ds18b20.cpp b/src/modules/sensors/Ds18b20/Ds18b20.cpp index e6518e03..495c3406 100644 --- a/src/modules/sensors/Ds18b20/Ds18b20.cpp +++ b/src/modules/sensors/Ds18b20/Ds18b20.cpp @@ -71,7 +71,7 @@ class Ds18b20 : public IoTItem { if (value.valD != DEVICE_DISCONNECTED_C) regEvent(value.valD, ""); //обязательный вызов для отправки результата работы else - SerialPrint("E", "Sensor Ds18b20", "Error"); + SerialPrint("E", "Sensor Ds18b20", "Error", _id); } //======================================================================================================= diff --git a/src/modules/sensors/Ds18b20/modinfo.json b/src/modules/sensors/Ds18b20/modinfo.json index f3ac951e..bf2ba4b0 100644 --- a/src/modules/sensors/Ds18b20/modinfo.json +++ b/src/modules/sensors/Ds18b20/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "DS18B20 Температура", "type": "Reading", "subtype": "Ds18b20", @@ -28,7 +29,7 @@ "esp8266_4mb": 15 }, "title": "Cенсор температуры ds18b20", - "moduleDesc": "Позволяет получить значения температуры с Ds18b20.", + "moduleDesc": "Позволяет получить значения температуры с Ds18b20. О подделках: https://github.com/cpetrich/counterfeit_DS18B20", "propInfo": { "pin": "GPIO номер, к которому подключена шина данных датчиков.", "index": "Порядковый номер датчика на шине.", @@ -43,6 +44,12 @@ ], "esp8266_4mb": [ "milesburton/DallasTemperature@^3.9.1" + ], + "esp8266_1mb": [ + "milesburton/DallasTemperature@^3.9.1" + ], + "esp8266_1mb_ota": [ + "milesburton/DallasTemperature@^3.9.1" ] } } \ No newline at end of file diff --git a/src/modules/sensors/Emon/modinfo.json b/src/modules/sensors/Emon/modinfo.json index 9f4086a8..e254359f 100644 --- a/src/modules/sensors/Emon/modinfo.json +++ b/src/modules/sensors/Emon/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "EMON Ток", "type": "Reading", "subtype": "I", @@ -16,6 +17,7 @@ "multiply": 1 }, { + "global": 0, "name": "EMON Напряжение", "type": "Reading", "subtype": "U", diff --git a/src/modules/sensors/FreqMeter/modinfo.json b/src/modules/sensors/FreqMeter/modinfo.json index c581802c..91eb8c82 100644 --- a/src/modules/sensors/FreqMeter/modinfo.json +++ b/src/modules/sensors/FreqMeter/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "Частотомер на ADC, Частота", "type": "Reading", "subtype": "FreqMeterF", @@ -18,6 +19,7 @@ "int": 5 }, { + "global": 0, "name": "Частотомер на ADC, Процент Пульсации", "type": "Reading", "subtype": "FreqMeterPcFl", @@ -34,6 +36,7 @@ "int": 5 }, { + "global": 0, "name": "Частотомер на ADC, Индекс Пульсации", "type": "Reading", "subtype": "FreqMeterFlIn", @@ -79,7 +82,7 @@ "int": "Количество секунд между опросами датчика." } }, - "defActive": true, + "defActive": false, "usedLibs": { "esp32_4mb": [ "kosme/arduinoFFT@^1.5.6" diff --git a/src/modules/sensors/GY21/GY21.cpp b/src/modules/sensors/GY21/GY21.cpp index 4c89722e..5af1923b 100644 --- a/src/modules/sensors/GY21/GY21.cpp +++ b/src/modules/sensors/GY21/GY21.cpp @@ -23,7 +23,7 @@ class GY21t : public IoTItem { if (value.valD < 300) regEvent(value.valD, "GY21"); // TODO: найти способ понимания ошибки получения данных else - SerialPrint("E", "Sensor GY21t", "Error"); + SerialPrint("E", "Sensor GY21t", "Error", _id); } ~GY21t(){}; @@ -39,7 +39,7 @@ class GY21h : public IoTItem { if (value.valD != 0) regEvent(value.valD, "GY21h"); // TODO: найти способ понимания ошибки получения данных else - SerialPrint("E", "Sensor GY21h", "Error"); + SerialPrint("E", "Sensor GY21h", "Error", _id); } ~GY21h(){}; diff --git a/src/modules/sensors/GY21/modinfo.json b/src/modules/sensors/GY21/modinfo.json index d81dd21c..7878e037 100644 --- a/src/modules/sensors/GY21/modinfo.json +++ b/src/modules/sensors/GY21/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "GY21 Температура", "type": "Reading", "subtype": "GY21t", @@ -13,6 +14,7 @@ "int": 15 }, { + "global": 0, "name": "GY21 Влажность", "type": "Reading", "subtype": "GY21h", diff --git a/src/modules/sensors/Hdc1080/Hdc1080.cpp b/src/modules/sensors/Hdc1080/Hdc1080.cpp index 6af1eff3..fe595787 100644 --- a/src/modules/sensors/Hdc1080/Hdc1080.cpp +++ b/src/modules/sensors/Hdc1080/Hdc1080.cpp @@ -24,7 +24,7 @@ class Hdc1080t : public IoTItem { if (value.valD < 124) regEvent(value.valD, "Hdc1080t"); else - SerialPrint("E", "Sensor Hdc1080t", "Error"); + SerialPrint("E", "Sensor Hdc1080t", "Error", _id); } ~Hdc1080t(){}; @@ -39,7 +39,7 @@ class Hdc1080h : public IoTItem { if (value.valD < 99) regEvent(value.valD, "Hdc1080h"); else - SerialPrint("E", "Sensor Hdc1080h", "Error"); + SerialPrint("E", "Sensor Hdc1080h", "Error", _id); } ~Hdc1080h(){}; diff --git a/src/modules/sensors/Hdc1080/modinfo.json b/src/modules/sensors/Hdc1080/modinfo.json index 7a873738..3250c7a9 100644 --- a/src/modules/sensors/Hdc1080/modinfo.json +++ b/src/modules/sensors/Hdc1080/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "HDC1080 Температура", "type": "Reading", "subtype": "Hdc1080t", @@ -14,6 +15,7 @@ "round": 1 }, { + "global": 0, "name": "HDC1080 Влажность", "type": "Reading", "subtype": "Hdc1080h", diff --git a/src/modules/sensors/Ina219/Ina219.cpp b/src/modules/sensors/Ina219/Ina219.cpp new file mode 100644 index 00000000..6ab65897 --- /dev/null +++ b/src/modules/sensors/Ina219/Ina219.cpp @@ -0,0 +1,136 @@ +/****************************************************************** + Used Adafruit INA219 Current Sensor + Support for INA219 + https://github.com/adafruit/Adafruit_INA219 + + adapted for version 4dev @Serghei63 + ******************************************************************/ + +#include "Global.h" +#include "classes/IoTItem.h" + +#include +#include + +Adafruit_INA219 ina219; + + float shuntvoltage = 0; + float busvoltage = 0; + float current_mA = 0; + float loadvoltage = 0; + float power_mW = 0; + + // shuntvoltage = ina219.getShuntVoltage_mV(); // Получение напряжение на шунте + // busvoltage = ina219.getBusVoltage_V(); // Получение значение напряжения V + // current_mA = ina219.getCurrent_mA(); // Получение значение тока в мА + // power_mW = ina219.getPower_mW(); // Получение значение мощности + // loadvoltage = busvoltage + (shuntvoltage / 1000); // Расчет напряжение на нагрузки + + class Ina219loadvoltage : public IoTItem { + public: + Ina219loadvoltage(String parameters) : IoTItem(parameters) { + + // Wire.begin(2,0); // Инициализация шины I2C для модуля E01 + } + + void doByInterval() { + + loadvoltage = busvoltage + (shuntvoltage / 1000); + value.valD = loadvoltage; + + regEvent(value.valD, "Ina219loadvoltage"); + } + + ~Ina219loadvoltage(){}; +}; + + class Ina219busvoltage : public IoTItem { + public: + Ina219busvoltage(String parameters) : IoTItem(parameters) { + + // Wire.begin(2,0); // Инициализация шины I2C для модуля E01 + } + + void doByInterval() { + + busvoltage = ina219.getBusVoltage_V(); + value.valD = busvoltage; + + regEvent(value.valD, "Ina219busvoltage"); + } + + ~Ina219busvoltage(){}; +}; + + +class Ina219curr : public IoTItem { + public: + Ina219curr(String parameters) : IoTItem(parameters) { + + // Wire.begin(2,0); // Инициализация шины I2C для модуля E01 + } + void doByInterval() { + + current_mA = ina219.getCurrent_mA(); + value.valD = current_mA; + + regEvent(value.valD, "Ina219curr"); + } + + ~Ina219curr(){}; +}; + +class Ina219shuntvoltage : public IoTItem { + public: + Ina219shuntvoltage(String parameters) : IoTItem(parameters) { + + // Wire.begin(2,0); // Инициализация шины I2C для модуля E01 + } + void doByInterval() { + + shuntvoltage = ina219.getShuntVoltage_mV(); + value.valD = shuntvoltage; + + regEvent(value.valD, "Ina219shuntvoltage"); + } + + ~Ina219shuntvoltage(){}; +}; + +class Power_mW : public IoTItem { + public: + Power_mW(String parameters) : IoTItem(parameters) { + + // Wire.begin(2,0); // Инициализация шины I2C для модуля E01 + } + void doByInterval() { + + power_mW = ina219.getPower_mW(); + value.valD = power_mW; + + regEvent(value.valD, "Ina219power"); // TODO: найти способ понимания ошибки получения данных + + } + + ~Power_mW(){}; +}; +void* getAPI_Ina219(String subtype, String param) { + if (subtype == F("Ina219curr")) { + ina219.begin(); + return new Ina219curr(param); + } else if (subtype == F("Ina219shuntvoltage")) { + ina219.begin(); + return new Ina219shuntvoltage(param); + } else if (subtype == F("power_mW")) { + ina219.begin(); + return new Power_mW(param); + } else if (subtype == F("Ina219busvoltage")) { + ina219.begin(); + return new Ina219busvoltage(param); + } else if (subtype == F("Ina219loadvoltage")) { + ina219.begin(); + return new Ina219loadvoltage(param); + } else { + return nullptr; + } +} diff --git a/src/modules/sensors/Ina219/modinfo.json b/src/modules/sensors/Ina219/modinfo.json new file mode 100644 index 00000000..1dc9638f --- /dev/null +++ b/src/modules/sensors/Ina219/modinfo.json @@ -0,0 +1,99 @@ +{ + "menuSection": "Сенсоры", + + "configItem": [{ + "global": 0, + "name": "INA219 Tок", + "type": "Reading", + "subtype": "Ina219curr", + "id": "Ina219current", + "widget": "anydatamAmp", + "page": "INA 219", + "descr": "219 Датчик тока", + "int": 10 + }, + { + "global": 0, + "name": "INA219 Напряжение", + "type": "Reading", + "subtype": "Ina219busvoltage", + "id": "Ina219busvoltage", + "widget": "anydataVlt", + "page": "INA 219", + "descr": "219 Датчик напряжения", + "int": 10 + }, + { + "global": 0, + "name": "INA219 Мощность", + "type": "Reading", + "subtype": "power_mW", + "id": "Ina219power", + "widget": "anydatamWtt", + "page": "INA 219", + "descr": "219 Мощность", + "int": 10 + }, + { + "global": 0, + "name": "INA219 Напряжение нагрузки", + "type": "Reading", + "subtype": "Ina219loadvoltage", + "id": "Ina219loadvoltage", + "widget": "anydataVlt", + "page": "INA 219", + "descr": "219 Напряжение нагрузки", + "int": 10 + }, + { + "global": 0, + "name": "INA219 Шунт", + "type": "Reading", + "subtype": "Ina219shuntvoltage", + "id": "Ina219shuntvoltage", + "widget": "anydatamVlt", + "page": "INA 219", + "descr": "219 Напряжение шунта", + "int": 10 + }], + + "about": { + "authorName": "Serghei Crasnicov", + "authorContact": "https://t.me/Serghei63", + "authorGit": "https://github.com/Serghei63", + "specialThanks": "Дмитрий , Serg", + "moduleName": "Ina219", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "subTypes": [ + "Ina219curr", + "Ina219busvoltage", + "power_mW", + "Ina219loadvoltage", + "Ina219shuntvoltage" + ], + "title": "Милливатметр постоянного тока", + "moduleDesc": "Измеряет постоянный ток до 3.2 ампера, напряжение до 26 вольт и мощность на нагрузке", + "propInfo": { + "int": "Количество секунд между опросами датчика." + } + }, + + "defActive": false, + + "usedLibs": { + "esp32_4mb": [ + "https://github.com/adafruit/Adafruit_INA219.git" + ], + + "esp8266_4mb": [ + "https://github.com/adafruit/Adafruit_INA219.git" + ] + } + + } + + \ No newline at end of file diff --git a/src/modules/sensors/IoTWiegand/modinfo.json b/src/modules/sensors/IoTWiegand/modinfo.json index 6e375f55..ff7457e2 100644 --- a/src/modules/sensors/IoTWiegand/modinfo.json +++ b/src/modules/sensors/IoTWiegand/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "Wiegand Считыватель", "type": "Reading", "subtype": "IoTWiegand", diff --git a/src/modules/sensors/Max6675/Max6675.cpp b/src/modules/sensors/Max6675/Max6675.cpp index 67ece7fd..e351602c 100644 --- a/src/modules/sensors/Max6675/Max6675.cpp +++ b/src/modules/sensors/Max6675/Max6675.cpp @@ -29,7 +29,7 @@ class MAX6675t : public IoTItem { if (String(value.valD) != "nan") { regEvent(value.valD, "Max6675t"); } else { - SerialPrint("E", "Sensor Max6675t", "Error"); + SerialPrint("E", "Sensor Max6675t", "Error", _id); } } diff --git a/src/modules/sensors/Max6675/modinfo.json b/src/modules/sensors/Max6675/modinfo.json index 8e091483..2523a69d 100644 --- a/src/modules/sensors/Max6675/modinfo.json +++ b/src/modules/sensors/Max6675/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "MAX6675 Температура", "type": "Reading", "subtype": "Max6675t", diff --git a/src/modules/sensors/Mhz19/modinfo.json b/src/modules/sensors/Mhz19/modinfo.json index 57b00496..638fe131 100644 --- a/src/modules/sensors/Mhz19/modinfo.json +++ b/src/modules/sensors/Mhz19/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "MHZ-19 CO2 UART", "type": "Reading", "subtype": "Mhz19uart", @@ -20,6 +21,7 @@ "ABC": 1 }, { + "global": 0, "name": "MHZ-19 CO2 PWM", "type": "Reading", "subtype": "Mhz19pwm", @@ -36,6 +38,7 @@ "int": 300 }, { + "global": 0, "name": "MHZ-19 Температура UART", "type": "Reading", "subtype": "Mhz19temp", diff --git a/src/modules/sensors/Pzem004t/modinfo.json b/src/modules/sensors/Pzem004t/modinfo.json index 2a25464f..d5a31eaf 100644 --- a/src/modules/sensors/Pzem004t/modinfo.json +++ b/src/modules/sensors/Pzem004t/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "PZEM 004t Напряжение", "type": "Reading", "subtype": "Pzem004v", @@ -14,6 +15,7 @@ "round": 1 }, { + "global": 0, "name": "PZEM 004t Сила тока", "type": "Reading", "subtype": "Pzem004a", @@ -26,6 +28,7 @@ "round": 1 }, { + "global": 0, "name": "PZEM 004t Мощность", "type": "Reading", "subtype": "Pzem004w", @@ -38,6 +41,7 @@ "round": 1 }, { + "global": 0, "name": "PZEM 004t Энергия", "type": "Reading", "subtype": "Pzem004wh", @@ -50,6 +54,7 @@ "round": 1 }, { + "global": 0, "name": "PZEM 004t Частота", "type": "Reading", "subtype": "Pzem004hz", @@ -62,6 +67,7 @@ "round": 1 }, { + "global": 0, "name": "PZEM 004t Косинус", "type": "Reading", "subtype": "Pzem004pf", @@ -102,11 +108,7 @@ }, "defActive": true, "usedLibs": { - "esp32_4mb": [ - "https://github.com/mandulaj/PZEM-004T-v30" - ], - "esp8266_4mb": [ - "https://github.com/mandulaj/PZEM-004T-v30" - ] + "esp32_4mb": [], + "esp8266_4mb": [] } } \ No newline at end of file diff --git a/src/modules/sensors/RCswitch/modinfo.json b/src/modules/sensors/RCswitch/modinfo.json index 2fcc452a..f5a6dc97 100644 --- a/src/modules/sensors/RCswitch/modinfo.json +++ b/src/modules/sensors/RCswitch/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [{ + "global": 0, "name": "Сканер кнопок 433 MHz", "num": 31, "type": "Reading", @@ -59,6 +60,9 @@ "esp8266_1mb": [ "rc-switch @ ^2.6.4" ], + "esp8266_1mb_ota": [ + "rc-switch @ ^2.6.4" + ], "esp8266_4mb": [ "rc-switch @ ^2.6.4" ] diff --git a/src/modules/sensors/Sds011/modinfo.json b/src/modules/sensors/Sds011/modinfo.json index 81a7eca6..8fd8eb80 100644 --- a/src/modules/sensors/Sds011/modinfo.json +++ b/src/modules/sensors/Sds011/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "SDS011 PM25 Пыль", "type": "Reading", "subtype": "Sds011_25", @@ -21,6 +22,7 @@ "retryDelayMs": 5 }, { + "global": 0, "name": "SDS011 PM10 Пыль", "type": "Reading", "subtype": "Sds011_10", diff --git a/src/modules/sensors/Sht20/Sht20.cpp b/src/modules/sensors/Sht20/Sht20.cpp index a9100974..85b0e508 100644 --- a/src/modules/sensors/Sht20/Sht20.cpp +++ b/src/modules/sensors/Sht20/Sht20.cpp @@ -16,7 +16,7 @@ class Sht20t : public IoTItem { if (value.valD > -46.85F) regEvent(value.valD, "Sht20t"); else - SerialPrint("E", "Sensor Sht20t", "Error"); + SerialPrint("E", "Sensor Sht20t", "Error", _id); } ~Sht20t(){}; @@ -32,7 +32,7 @@ class Sht20h : public IoTItem { if (value.valD != -6) regEvent(value.valD, "Sht20h"); else - SerialPrint("E", "Sensor Sht20h", "Error"); + SerialPrint("E", "Sensor Sht20h", "Error", _id); } ~Sht20h(){}; diff --git a/src/modules/sensors/Sht20/modinfo.json b/src/modules/sensors/Sht20/modinfo.json index 6d8d745d..797390dc 100644 --- a/src/modules/sensors/Sht20/modinfo.json +++ b/src/modules/sensors/Sht20/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "Sht20 Температура", "type": "Reading", "subtype": "Sht20t", @@ -13,6 +14,7 @@ "round": 1 }, { + "global": 0, "name": "Sht20 Влажность", "type": "Reading", "subtype": "Sht20h", diff --git a/src/modules/sensors/Sht30/Sht30.cpp b/src/modules/sensors/Sht30/Sht30.cpp index d0a927b6..d78947df 100644 --- a/src/modules/sensors/Sht30/Sht30.cpp +++ b/src/modules/sensors/Sht30/Sht30.cpp @@ -26,7 +26,7 @@ class Sht30t : public IoTItem { SerialPrint("E", "Sensor Sht30t", "OK"); if (value.valD < -46.85F) regEvent(value.valD, "Sht30t"); // TODO: найти способ понимания ошибки получения данных - else SerialPrint("E", "Sensor Sht30t", "Error"); + else SerialPrint("E", "Sensor Sht30t", "Error", _id); } } ~Sht30t() {}; @@ -42,7 +42,7 @@ class Sht30h : public IoTItem { SerialPrint("E", "Sensor Sht30h", "OK"); if (value.valD != -6) regEvent(value.valD, "Sht30h"); // TODO: найти способ понимания ошибки получения данных - else SerialPrint("E", "Sensor Sht30h", "Error"); + else SerialPrint("E", "Sensor Sht30h", "Error", _id); } } ~Sht30h() {}; diff --git a/src/modules/sensors/Sht30/modinfo.json b/src/modules/sensors/Sht30/modinfo.json index ca722253..df3c8ec7 100644 --- a/src/modules/sensors/Sht30/modinfo.json +++ b/src/modules/sensors/Sht30/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "Sht30 Температура", "type": "Reading", "subtype": "Sht30t", @@ -13,6 +14,7 @@ "round": 1 }, { + "global": 0, "name": "Sht30 Влажность", "type": "Reading", "subtype": "Sht30h", diff --git a/src/modules/sensors/Sonar/modinfo.json b/src/modules/sensors/Sonar/modinfo.json index e944043c..be4911de 100644 --- a/src/modules/sensors/Sonar/modinfo.json +++ b/src/modules/sensors/Sonar/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Сенсоры", "configItem": [ { + "global": 0, "name": "HC-SR04 Ультразвуковой дальномер", "num": 1, "type": "Reading", @@ -37,6 +38,8 @@ "defActive": true, "usedLibs": { "esp32_4mb": [], - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } \ No newline at end of file diff --git a/src/modules/sensors/UART/Uart.cpp b/src/modules/sensors/UART/Uart.cpp index 052acb61..a6d5040e 100644 --- a/src/modules/sensors/UART/Uart.cpp +++ b/src/modules/sensors/UART/Uart.cpp @@ -6,58 +6,199 @@ #include "modules/sensors/UART/Uart.h" #ifdef ESP8266 -SoftwareSerial* myUART = nullptr; + SoftwareSerial* myUART = nullptr; #else -HardwareSerial* myUART = nullptr; + HardwareSerial* myUART = nullptr; #endif class UART : public IoTItem { private: - int tx; - int rx; - int speed; + int _eventFormat = 0; // 0 - нет приема, 1 - json IoTM, 2 - Nextion + +#ifdef ESP8266 + SoftwareSerial* _myUART = nullptr; +#else + HardwareSerial* _myUART = nullptr; +#endif public: UART(String parameters) : IoTItem(parameters) { - tx = jsonReadInt(parameters, "tx"); - rx = jsonReadInt(parameters, "rx"); - speed = jsonReadInt(parameters, "speed"); + int _tx, _rx, _speed, _line; + jsonRead(parameters, "tx", _tx); + jsonRead(parameters, "rx", _rx); + jsonRead(parameters, "speed", _speed); + jsonRead(parameters, "line", _line); + jsonRead(parameters, "eventFormat", _eventFormat); - if (!myUART) { #ifdef ESP8266 - myUART = new SoftwareSerial(tx, rx); - myUART->begin(speed); + myUART = _myUART = new SoftwareSerial(_rx, _tx); + _myUART->begin(_speed); #endif #ifdef ESP32 - myUART = new HardwareSerial(2); - myUART->begin(speed, SERIAL_8N1, rx, tx); + myUART = _myUART = new HardwareSerial(_line); + _myUART->begin(_speed, SERIAL_8N1, _rx, _tx); #endif + } + + // проверяем формат и если событие, то регистрируем его + void analyzeString(const String& msg) { + switch (_eventFormat) { + case 0: // не указан формат, значит все полученные данные воспринимаем как общее значение из UART + setValue(msg); + break; + + case 1: // формат событий IoTM с использованием json, значит создаем временную копию + analyzeMsgFromNet(msg); + break; + + case 2: // формат команд от Nextion ID=Value + if (msg.indexOf("=") == -1) { // если входящее сообщение не по формату, то работаем как в режиме 0 + setValue(msg); + break; + } + String id = selectToMarker(msg, "="); + String valStr = selectToMarkerLast(msg, "="); + valStr.replace("\"", ""); + id.replace(".val", "_val"); + id.replace(".txt", "_txt"); + generateOrder(id, valStr); + break; } - SerialPrint("i", F("UART"), F("UART Init")); } void uartHandle() { - if (myUART) { - static String incStr; - if (myUART->available()) { - char inc; - inc = myUART->read(); - incStr += inc; - if (inc == '\n') { - parse(incStr); - incStr = ""; - } + if (!_myUART) return; + + if (_myUART->available()) { + static String inStr = ""; + char inc; + + inc = _myUART->read(); + if (inc == 0xFF) { + inc = _myUART->read(); + inc = _myUART->read(); + inStr = ""; + return; } + + if (inc == '\r') return; + + if (inc == '\n') { + analyzeString(inStr); + inStr = ""; + } else inStr += inc; } } - void parse(String& incStr) { - SerialPrint("I", "=>UART", incStr); + void onRegEvent(IoTItem* eventItem) { + if (!_myUART || !eventItem) return; + + String printStr = ""; + switch (_eventFormat) { + case 0: return; // не указан формат, значит не следим за событиями + case 1: // формат событий IoTM с использованием json + if (eventItem->isGlobal()) { + eventItem->getNetEvent(printStr); + _myUART->println(printStr); + } + break; + + case 2: // формат событий для Nextion ID=Value0xFF0xFF0xFF + printStr += eventItem->getID(); + int indexOf_ = printStr.indexOf("_"); + //Serial.println(printStr + " fff " + 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(); + } + + uartPrintFFF(printStr); + break; + } } - void uartPrint(String msg) { - myUART->print(msg); + virtual void loop() { + uartHandle(); } + + void uartPrintFFF(const String& msg) { + if (_myUART) { + _myUART->print(msg); + _myUART->write(0xff); + _myUART->write(0xff); + _myUART->write(0xff); + } + } + + void uartPrintln(const String& msg) { + if (_myUART) { + _myUART->println(msg); + } + } + + void uartPrint(const String& msg) { + if (_myUART) { + _myUART->print(msg); + } + } + + void uartPrintHex(const String& msg) { + if (!_myUART) return; + + unsigned char Hi, Lo; + uint8_t byteTx; + const char* strPtr = msg.c_str(); + while ((Hi = *strPtr++) && (Lo = *strPtr++)) { + byteTx = (ChartoHex(Hi) << 4) | ChartoHex(Lo); + _myUART->write(byteTx); + } + } + + IoTValue execute(String command, std::vector ¶m) { + if (command == "println") { + if (param.size() == 1) { + if (param[0].isDecimal) uartPrintln((String)param[0].valD); + else uartPrintln(param[0].valS); + } + } else if (command == "print") { + if (param.size() == 1) { + if (param[0].isDecimal) uartPrint((String)param[0].valD); + else uartPrint(param[0].valS); + } + } else if (command == "printHex") { + if (param.size() == 1) { + uartPrintHex(param[0].valS); + } + } else if (command == "printFFF") { + if (param.size() == 2) { + String strToUart = ""; + if (param[0].isDecimal) + strToUart = param[0].valD; + else + strToUart = param[0].valS; + + if (param[1].valD) + uartPrintFFF("\"" + strToUart + "\""); + else + uartPrintFFF(strToUart); + } + } + + return {}; + } + }; void* getAPI_UART(String subtype, String param) { diff --git a/src/modules/sensors/UART/modinfo.json b/src/modules/sensors/UART/modinfo.json index 7f0dc67d..cc6a0999 100644 --- a/src/modules/sensors/UART/modinfo.json +++ b/src/modules/sensors/UART/modinfo.json @@ -11,7 +11,9 @@ "id": "u", "tx": 12, "rx": 13, - "speed": 9600 + "line": 2, + "speed": 9600, + "eventFormat": 0 } ], "about": { @@ -29,12 +31,37 @@ "SoftUART" ], "title": "Software uart для esp8266 или hardware uart для esp32", - "moduleDesc": "Используется вместе с Pzem004t или с другими работающими по uart сенсорами, в последствии будет доработан для связи с arduino платами", + "moduleDesc": "Используется вместе с Pzem004t или с другими работающими по uart сенсорами. Пригоден для обмена данными с другими контроллерами в ручном режиме или с автоматической трансляцией событий как по сети.", "propInfo": { "tx": "TX пин", "rx": "RX пин", - "speed": "Скорость UART" - } + "speed": "Скорость UART", + "line": "Актуально только для ESP32: номер линии hardUART. =2 rx=16 tx=17", + "eventFormat": "Выбор формата обмена сообщениями с другими контроллерами. =0 - не указан формат, значит не следим за событиями, =1 - формат событий IoTM с использованием json, =2 - формат событий для Nextion отправка событий: ID.val=Value0xFF0xFF0xFF прием ордеров: ID=Value. Отправляться будут события тех элементов, которые имеют суффикс в ИД _val или _txt, которые влияют на передаваемый формат." + }, + "retInfo": "Содержит полученное последнее по UART сообщение.", + "funcInfo": [ + { + "name": "println", + "descr": "Отправить в UART строку текста и признак завершения строки (перевод строки).", + "params": ["Строка текста"] + }, + { + "name": "print", + "descr": "Отправить в UART строку текста.", + "params": ["Строка текста"] + }, + { + "name": "printHex", + "descr": "Отправить в UART HEX-строку.", + "params": ["HEX-строка."] + }, + { + "name": "printFFF", + "descr": "Отправить в UART текстовую строку и hex метку 3 байта 0xFF0xFF0xFF.", + "params": ["Строка текста", "1 - обернуть строку в кавычки, 0 - отправить без кавычек"] + } + ] }, "defActive": true, "usedLibs": { diff --git a/src/modules/virtual/Cron/Cron.cpp b/src/modules/virtual/Cron/Cron.cpp new file mode 100644 index 00000000..2c24eb59 --- /dev/null +++ b/src/modules/virtual/Cron/Cron.cpp @@ -0,0 +1,111 @@ +#include "NTP.h" +#include "Global.h" +#include "classes/IoTItem.h" + +extern "C" { +#include "ccronexpr/ccronexpr.h" +} + + +class Cron : public IoTItem { + private: + bool _pause = false; + String _format = ""; + cron_expr _expr; + time_t _nextAlarm = 0; + + public: + Cron(String parameters): IoTItem(parameters) { + jsonRead(parameters, F("formatNextAlarm"), _format); + initCron(); + } + + void initCron() { + const char* err = NULL; + memset(&_expr, 0, sizeof(_expr)); + + cron_parse_expr(value.valS.c_str(), &_expr, &err); + if (err) { + _pause = true; + _nextAlarm = 0; + memset(&_expr, 0, sizeof(_expr)); + SerialPrint("E", "Cron", F("The Cron string did not apply."), _id); + } else + updateNextAlarm(true); + } + + void updateNextAlarm(bool forced) { + if (!_pause && _time_isTrust) { + if (forced || (_nextAlarm <= gmtTimeToLocal(unixTime))) { + // update alarm if next trigger is not yet in the future + _nextAlarm = cron_next(&_expr, gmtTimeToLocal(unixTime)); + } + } + } + + String getNextAlarmF() { + if (_pause) return "Pause"; + if (!_time_isTrust) return "No time"; + struct tm* timeinfo; + char buffer [80]; + timeinfo = localtime(&_nextAlarm); + strftime(buffer, 80, _format.c_str(), timeinfo); + return buffer; + } + + String getValue() { + return getNextAlarmF(); + } + + void setValue(const IoTValue& Value, bool genEvent) { + value = Value; + _pause = false; + initCron(); + + if (_needSave) { + jsonWriteStr_(valuesFlashJson, _id, value.valS); + needSaveValues = true; + } + + bool _needSaveBak = _needSave; + _needSave = false; + regEvent(getNextAlarmF(), F("Cron alarm"), false, false); + _needSave = _needSaveBak; + } + + void doByInterval() { + if (!_pause && _time_isTrust && (gmtTimeToLocal(unixTime) >= _nextAlarm)) { + updateNextAlarm(true); + bool _needSaveBak = _needSave; + _needSave = false; + regEvent(getNextAlarmF(), F("Cron alarm")); + _needSave = _needSaveBak; + } + } + + IoTValue execute(String command, std::vector ¶m) { + if (command == "stop") { + _pause = true; + } else if (command == "continue") { + _pause = false; + updateNextAlarm(false); + } + + bool _needSaveBak = _needSave; + _needSave = false; + regEvent(getNextAlarmF(), F("Cron alarm"), false, false); + _needSave = _needSaveBak; + + return {}; + } + + ~Cron() {}; +}; + +void* getAPI_Cron(String subtype, String param) { + if (subtype == F("Cron")) { + return new Cron(param); + } else { + return nullptr; + } +} diff --git a/src/modules/virtual/Cron/ccronexpr/ccronexpr.c b/src/modules/virtual/Cron/ccronexpr/ccronexpr.c new file mode 100644 index 00000000..d9715ff7 --- /dev/null +++ b/src/modules/virtual/Cron/ccronexpr/ccronexpr.c @@ -0,0 +1,1272 @@ +/* + * Copyright 2015, alex at staticlibs.net + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * File: ccronexpr.c + * Author: alex + * + * Created on February 24, 2015, 9:35 AM + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ccronexpr.h" + +#define CRON_MAX_SECONDS 60 +#define CRON_MAX_MINUTES 60 +#define CRON_MAX_HOURS 24 +#define CRON_MAX_DAYS_OF_WEEK 8 +#define CRON_MAX_DAYS_OF_MONTH 32 +#define CRON_MAX_MONTHS 12 +#define CRON_MAX_YEARS_DIFF 4 + +#define CRON_CF_SECOND 0 +#define CRON_CF_MINUTE 1 +#define CRON_CF_HOUR_OF_DAY 2 +#define CRON_CF_DAY_OF_WEEK 3 +#define CRON_CF_DAY_OF_MONTH 4 +#define CRON_CF_MONTH 5 +#define CRON_CF_YEAR 6 + +#define CRON_CF_ARR_LEN 7 + +#define CRON_INVALID_INSTANT ((time_t) -1) + +static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; +#define CRON_DAYS_ARR_LEN 7 +static const char* const MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#define CRON_MONTHS_ARR_LEN 13 + +#define CRON_MAX_STR_LEN_TO_SPLIT 256 +#define CRON_MAX_NUM_TO_SRING 1000000000 +/* computes number of digits in decimal number */ +#define CRON_NUM_OF_DIGITS(num) (abs(num) < 10 ? 1 : \ + (abs(num) < 100 ? 2 : \ + (abs(num) < 1000 ? 3 : \ + (abs(num) < 10000 ? 4 : \ + (abs(num) < 100000 ? 5 : \ + (abs(num) < 1000000 ? 6 : \ + (abs(num) < 10000000 ? 7 : \ + (abs(num) < 100000000 ? 8 : \ + (abs(num) < 1000000000 ? 9 : 10))))))))) + +#ifndef CRON_TEST_MALLOC +#define cron_malloc(x) malloc(x); +#define cron_free(x) free(x); +#else /* CRON_TEST_MALLOC */ +void* cron_malloc(size_t n); +void cron_free(void* p); +#endif /* CRON_TEST_MALLOC */ + +/** + * Time functions from standard library. + * This part defines: cron_mktime: create time_t from tm + * cron_time: create tm from time_t + */ + +/* forward declarations for platforms that may need them */ +/* can be hidden in time.h */ +#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ANDROID) +struct tm *gmtime_r(const time_t *timep, struct tm *result); +time_t timegm(struct tm* __tp); +struct tm *localtime_r(const time_t *timep, struct tm *result); +#endif /* PLEASE CHECK _WIN32 AND ANDROID NEEDS FOR THESE DECLARATIONS */ +#ifdef __MINGW32__ +/* To avoid warning when building with mingw */ +time_t _mkgmtime(struct tm* tm); +#endif /* __MINGW32__ */ + +/* function definitions */ +time_t cron_mktime_gm(struct tm* tm) { +#if defined(_WIN32) +/* http://stackoverflow.com/a/22557778 */ + return _mkgmtime(tm); +#elif defined(__AVR__) +/* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ + return mk_gmtime(tm); +#elif defined(ESP8266) + /* https://linux.die.net/man/3/timegm */ + /* http://www.catb.org/esr/time-programming/ */ + /* portable version of timegm() */ + time_t ret; + char *tz; + tz = getenv("TZ"); + if (tz) + tz = strdup(tz); + setenv("TZ", "UTC+0", 1); + tzset(); + ret = mktime(tm); + if (tz) { + setenv("TZ", tz, 1); + free(tz); + } else + unsetenv("TZ"); + tzset(); + return ret; +#elif defined(ANDROID) + /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */ + static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); + static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); + time64_t result = timegm64(tm); + if (result < kTimeMin || result > kTimeMax) return -1; + return result; +#else + return timegm(tm); +#endif +} + +struct tm* cron_time_gm(time_t* date, struct tm* out) { +#if defined(__MINGW32__) + (void)(out); /* To avoid unused warning */ + return gmtime(date); +#elif defined(_WIN32) + errno_t err = gmtime_s(out, date); + return 0 == err ? out : NULL; +#elif defined(__AVR__) + /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ + gmtime_r(date, out); + return out; +#else + return gmtime_r(date, out); +#endif +} + +time_t cron_mktime_local(struct tm* tm) { + tm->tm_isdst = -1; + return mktime(tm); +} + +struct tm* cron_time_local(time_t* date, struct tm* out) { +#if defined(_WIN32) + errno_t err = localtime_s(out, date); + return 0 == err ? out : NULL; +#elif defined(__AVR__) + /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ + localtime_r(date, out); + return out; +#else + return localtime_r(date, out); +#endif +} + +/* Defining 'cron_' time functions to use use UTC (default) or local time */ +#ifndef CRON_USE_LOCAL_TIME +time_t cron_mktime(struct tm* tm) { + return cron_mktime_gm(tm); +} + +struct tm* cron_time(time_t* date, struct tm* out) { + return cron_time_gm(date, out); +} + +#else /* CRON_USE_LOCAL_TIME */ +time_t cron_mktime(struct tm* tm) { + return cron_mktime_local(tm); +} + +struct tm* cron_time(time_t* date, struct tm* out) { + return cron_time_local(date, out); +} + +#endif /* CRON_USE_LOCAL_TIME */ + +/** + * Functions. + */ + +void cron_set_bit(uint8_t* rbyte, int idx) { + uint8_t j = (uint8_t) (idx / 8); + uint8_t k = (uint8_t) (idx % 8); + + rbyte[j] |= (1 << k); +} + +void cron_del_bit(uint8_t* rbyte, int idx) { + uint8_t j = (uint8_t) (idx / 8); + uint8_t k = (uint8_t) (idx % 8); + + rbyte[j] &= ~(1 << k); +} + +uint8_t cron_get_bit(uint8_t* rbyte, int idx) { + uint8_t j = (uint8_t) (idx / 8); + uint8_t k = (uint8_t) (idx % 8); + + if (rbyte[j] & (1 << k)) { + return 1; + } else { + return 0; + } +} + +static void free_splitted(char** splitted, size_t len) { + size_t i; + if (!splitted) return; + for (i = 0; i < len; i++) { + if (splitted[i]) { + cron_free(splitted[i]); + } + } + cron_free(splitted); +} + +static char* strdupl(const char* str, size_t len) { + if (!str) return NULL; + char* res = (char*) cron_malloc(len + 1); + if (!res) return NULL; + memset(res, 0, len + 1); + memcpy(res, str, len); + return res; +} + +static unsigned int next_set_bit(uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) { + unsigned int i; + if (!bits) { + *notfound = 1; + return 0; + } + for (i = from_index; i < max; i++) { + if (cron_get_bit(bits, i)) return i; + } + *notfound = 1; + return 0; +} + +static void push_to_fields_arr(int* arr, int fi) { + int i; + if (!arr || -1 == fi) { + return; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (arr[i] == fi) return; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (-1 == arr[i]) { + arr[i] = fi; + return; + } + } +} + +static int add_to_field(struct tm* calendar, int field, int val) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = calendar->tm_sec + val; + break; + case CRON_CF_MINUTE: + calendar->tm_min = calendar->tm_min + val; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = calendar->tm_hour + val; + break; + case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = calendar->tm_mday + val; + break; + case CRON_CF_MONTH: + calendar->tm_mon = calendar->tm_mon + val; + break; + case CRON_CF_YEAR: + calendar->tm_year = calendar->tm_year + val; + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +/** + * Reset the calendar setting all the fields provided to zero. + */ +static int reset_min(struct tm* calendar, int field) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = 0; + break; + case CRON_CF_MINUTE: + calendar->tm_min = 0; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = 0; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = 0; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = 1; + break; + case CRON_CF_MONTH: + calendar->tm_mon = 0; + break; + case CRON_CF_YEAR: + calendar->tm_year = 0; + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +static int reset_all_min(struct tm* calendar, int* fields) { + int i; + int res = 0; + if (!calendar || !fields) { + return 1; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (-1 != fields[i]) { + res = reset_min(calendar, fields[i]); + if (0 != res) return res; + } + } + return 0; +} + +static int set_field(struct tm* calendar, int field, int val) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = val; + break; + case CRON_CF_MINUTE: + calendar->tm_min = val; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = val; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = val; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = val; + break; + case CRON_CF_MONTH: + calendar->tm_mon = val; + break; + case CRON_CF_YEAR: + calendar->tm_year = val; + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +/** + * Search the bits provided for the next set bit after the value provided, + * and reset the calendar. + */ +static unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { + int notfound = 0; + int err = 0; + unsigned int next_value = next_set_bit(bits, max, value, ¬found); + /* roll over if needed */ + if (notfound) { + err = add_to_field(calendar, nextField, 1); + if (err) goto return_error; + err = reset_min(calendar, field); + if (err) goto return_error; + notfound = 0; + next_value = next_set_bit(bits, max, 0, ¬found); + } + if (notfound || next_value != value) { + err = set_field(calendar, field, next_value); + if (err) goto return_error; + err = reset_all_min(calendar, lower_orders); + if (err) goto return_error; + } + return next_value; + + return_error: + *res_out = 1; + return 0; +} + +static unsigned int find_next_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { + int err; + unsigned int count = 0; + unsigned int max = 366; + while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { + err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1); + + if (err) goto return_error; + day_of_month = calendar->tm_mday; + day_of_week = calendar->tm_wday; + reset_all_min(calendar, resets); + } + return day_of_month; + + return_error: + *res_out = 1; + return 0; +} + +static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { + int i; + int res = 0; + int* resets = NULL; + int* empty_list = NULL; + unsigned int second = 0; + unsigned int update_second = 0; + unsigned int minute = 0; + unsigned int update_minute = 0; + unsigned int hour = 0; + unsigned int update_hour = 0; + unsigned int day_of_week = 0; + unsigned int day_of_month = 0; + unsigned int update_day_of_month = 0; + unsigned int month = 0; + unsigned int update_month = 0; + + resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!resets) goto return_result; + empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!empty_list) goto return_result; + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + resets[i] = -1; + empty_list[i] = -1; + } + + second = calendar->tm_sec; + update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); + if (0 != res) goto return_result; + if (second == update_second) { + push_to_fields_arr(resets, CRON_CF_SECOND); + } + + minute = calendar->tm_min; + update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); + if (0 != res) goto return_result; + if (minute == update_minute) { + push_to_fields_arr(resets, CRON_CF_MINUTE); + } else { + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + + hour = calendar->tm_hour; + update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); + if (0 != res) goto return_result; + if (hour == update_hour) { + push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); + } else { + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + + day_of_week = calendar->tm_wday; + day_of_month = calendar->tm_mday; + update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); + if (0 != res) goto return_result; + if (day_of_month == update_day_of_month) { + push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); + } else { + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + + month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ + update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); + if (0 != res) goto return_result; + if (month != update_month) { + if (calendar->tm_year - dot > 4) { + res = -1; + goto return_result; + } + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + goto return_result; + + return_result: + if (!resets || !empty_list) { + res = -1; + } + if (resets) { + cron_free(resets); + } + if (empty_list) { + cron_free(empty_list); + } + return res; +} + +static int to_upper(char* str) { + if (!str) return 1; + int i; + for (i = 0; '\0' != str[i]; i++) { + int c = (int)str[i]; + str[i] = (char) toupper(c); + } + return 0; +} + +static char* to_string(int num) { + if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; + char* str = (char*) cron_malloc(CRON_NUM_OF_DIGITS(num) + 1); + if (!str) return NULL; + int res = sprintf(str, "%d", num); + if (res < 0) { + cron_free(str); + return NULL; + } + return str; +} + +static char* str_replace(char *orig, const char *rep, const char *with) { + char *result; /* the return string */ + char *ins; /* the next insert point */ + char *tmp; /* varies */ + size_t len_rep; /* length of rep */ + size_t len_with; /* length of with */ + size_t len_front; /* distance between rep and end of last rep */ + int count; /* number of replacements */ + if (!orig) return NULL; + if (!rep) rep = ""; + if (!with) with = ""; + len_rep = strlen(rep); + len_with = strlen(with); + + ins = orig; + for (count = 0; NULL != (tmp = strstr(ins, rep)); ++count) { + ins = tmp + len_rep; + } + + /* first time through the loop, all the variable are set correctly + from here on, + tmp points to the end of the result string + ins points to the next occurrence of rep in orig + orig points to the remainder of orig after "end of rep" + */ + tmp = result = (char*) cron_malloc(strlen(orig) + (len_with - len_rep) * count + 1); + if (!result) return NULL; + + while (count--) { + ins = strstr(orig, rep); + len_front = ins - orig; + tmp = strncpy(tmp, orig, len_front) + len_front; + tmp = strcpy(tmp, with) + len_with; + orig += len_front + len_rep; /* move to next "end of rep" */ + } + strcpy(tmp, orig); + return result; +} + +static unsigned int parse_uint(const char* str, int* errcode) { + char* endptr; + errno = 0; + long int l = strtol(str, &endptr, 0); + if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) { + *errcode = 1; + return 0; + } else { + *errcode = 0; + return (unsigned int) l; + } +} + +static char** split_str(const char* str, char del, size_t* len_out) { + size_t i; + size_t stlen = 0; + size_t len = 0; + int accum = 0; + char* buf = NULL; + char** res = NULL; + size_t bi = 0; + size_t ri = 0; + char* tmp; + + if (!str) goto return_error; + for (i = 0; '\0' != str[i]; i++) { + stlen += 1; + if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error; + } + + for (i = 0; i < stlen; i++) { + int c = str[i]; + if (del == str[i]) { + if (accum > 0) { + len += 1; + accum = 0; + } + } else if (!isspace(c)) { + accum += 1; + } + } + /* tail */ + if (accum > 0) { + len += 1; + } + if (0 == len) return NULL; + + buf = (char*) cron_malloc(stlen + 1); + if (!buf) goto return_error; + memset(buf, 0, stlen + 1); + res = (char**) cron_malloc(len * sizeof(char*)); + if (!res) goto return_error; + memset(res, 0, len * sizeof(char*)); + + for (i = 0; i < stlen; i++) { + int c = str[i]; + if (del == str[i]) { + if (bi > 0) { + tmp = strdupl(buf, bi); + if (!tmp) goto return_error; + res[ri++] = tmp; + memset(buf, 0, stlen + 1); + bi = 0; + } + } else if (!isspace(c)) { + buf[bi++] = str[i]; + } + } + /* tail */ + if (bi > 0) { + tmp = strdupl(buf, bi); + if (!tmp) goto return_error; + res[ri++] = tmp; + } + cron_free(buf); + *len_out = len; + return res; + + return_error: + if (buf) { + cron_free(buf); + } + free_splitted(res, len); + *len_out = 0; + return NULL; +} + +static char* replace_ordinals(char* value, const char* const * arr, size_t arr_len) { + size_t i; + char* cur = value; + char* res = NULL; + int first = 1; + for (i = 0; i < arr_len; i++) { + char* strnum = to_string((int) i); + if (!strnum) { + if (!first) { + cron_free(cur); + } + return NULL; + } + res = str_replace(cur, arr[i], strnum); + cron_free(strnum); + if (!first) { + cron_free(cur); + } + if (!res) { + return NULL; + } + cur = res; + if (first) { + first = 0; + } + } + return res; +} + +static int has_char(char* str, char ch) { + size_t i; + size_t len = 0; + if (!str) return 0; + len = strlen(str); + for (i = 0; i < len; i++) { + if (str[i] == ch) return 1; + } + return 0; +} + +static unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { + + char** parts = NULL; + size_t len = 0; + unsigned int* res = (unsigned int*) cron_malloc(2 * sizeof(unsigned int)); + if (!res) goto return_error; + + res[0] = 0; + res[1] = 0; + if (1 == strlen(field) && '*' == field[0]) { + res[0] = min; + res[1] = max - 1; + } else if (!has_char(field, '-')) { + int err = 0; + unsigned int val = parse_uint(field, &err); + if (err) { + *error = "Unsigned integer parse error 1"; + goto return_error; + } + + res[0] = val; + res[1] = val; + } else { + parts = split_str(field, '-', &len); + if (2 != len) { + *error = "Specified range requires two fields"; + goto return_error; + } + int err = 0; + res[0] = parse_uint(parts[0], &err); + if (err) { + *error = "Unsigned integer parse error 2"; + goto return_error; + } + res[1] = parse_uint(parts[1], &err); + if (err) { + *error = "Unsigned integer parse error 3"; + goto return_error; + } + } + if (res[0] >= max || res[1] >= max) { + *error = "Specified range exceeds maximum"; + goto return_error; + } + if (res[0] < min || res[1] < min) { + *error = "Specified range is less than minimum"; + goto return_error; + } + if (res[0] > res[1]) { + *error = "Specified range start exceeds range end"; + goto return_error; + } + + free_splitted(parts, len); + *error = NULL; + return res; + + return_error: + free_splitted(parts, len); + if (res) { + cron_free(res); + } + + return NULL; +} + +static void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { + size_t i; + unsigned int i1; + size_t len = 0; + + char** fields = split_str(value, ',', &len); + if (!fields) { + *error = "Comma split error"; + goto return_result; + } + + for (i = 0; i < len; i++) { + if (!has_char(fields[i], '/')) { + /* Not an incrementer so it must be a range (possibly empty) */ + + unsigned int* range = get_range(fields[i], min, max, error); + + if (*error) { + if (range) { + cron_free(range); + } + goto return_result; + + } + + for (i1 = range[0]; i1 <= range[1]; i1++) { + cron_set_bit(target, i1); + + } + cron_free(range); + + } else { + size_t len2 = 0; + char** split = split_str(fields[i], '/', &len2); + if (2 != len2) { + *error = "Incrementer must have two fields"; + free_splitted(split, len2); + goto return_result; + } + unsigned int* range = get_range(split[0], min, max, error); + if (*error) { + if (range) { + cron_free(range); + } + free_splitted(split, len2); + goto return_result; + } + if (!has_char(split[0], '-')) { + range[1] = max - 1; + } + int err = 0; + unsigned int delta = parse_uint(split[1], &err); + if (err) { + *error = "Unsigned integer parse error 4"; + cron_free(range); + free_splitted(split, len2); + goto return_result; + } + if (0 == delta) { + *error = "Incrementer may not be zero"; + cron_free(range); + free_splitted(split, len2); + goto return_result; + } + for (i1 = range[0]; i1 <= range[1]; i1 += delta) { + cron_set_bit(target, i1); + } + free_splitted(split, len2); + cron_free(range); + + } + } + goto return_result; + + return_result: + free_splitted(fields, len); + +} + +static void set_months(char* value, uint8_t* targ, const char** error) { + unsigned int i; + unsigned int max = 12; + + char* replaced = NULL; + + to_upper(value); + replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); + if (!replaced) { + *error = "Invalid month format"; + return; + } + set_number_hits(replaced, targ, 1, max + 1, error); + cron_free(replaced); + + /* ... and then rotate it to the front of the months */ + for (i = 1; i <= max; i++) { + if (cron_get_bit(targ, i)) { + cron_set_bit(targ, i - 1); + cron_del_bit(targ, i); + } + } +} + +static void set_days_of_week(char* field, uint8_t* targ, const char** error) { + unsigned int max = 7; + char* replaced = NULL; + + if (1 == strlen(field) && '?' == field[0]) { + field[0] = '*'; + } + to_upper(field); + replaced = replace_ordinals(field, DAYS_ARR, CRON_DAYS_ARR_LEN); + if (!replaced) { + *error = "Invalid day format"; + return; + } + set_number_hits(replaced, targ, 0, max + 1, error); + cron_free(replaced); + if (cron_get_bit(targ, 7)) { + /* Sunday can be represented as 0 or 7*/ + cron_set_bit(targ, 0); + cron_del_bit(targ, 7); + } +} + +static void set_days_of_month(char* field, uint8_t* targ, const char** error) { + /* Days of month start with 1 (in Cron and Calendar) so add one */ + if (1 == strlen(field) && '?' == field[0]) { + field[0] = '*'; + } + set_number_hits(field, targ, 1, CRON_MAX_DAYS_OF_MONTH, error); +} + +void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { + const char* err_local; + size_t len = 0; + char** fields = NULL; + if (!error) { + error = &err_local; + } + *error = NULL; + if (!expression) { + *error = "Invalid NULL expression"; + goto return_res; + } + if (!target) { + *error = "Invalid NULL target"; + goto return_res; + } + + fields = split_str(expression, ' ', &len); + if (len != 6) { + *error = "Invalid number of fields, expression must consist of 6 fields"; + goto return_res; + } + memset(target, 0, sizeof(*target)); + set_number_hits(fields[0], target->seconds, 0, 60, error); + if (*error) goto return_res; + set_number_hits(fields[1], target->minutes, 0, 60, error); + if (*error) goto return_res; + set_number_hits(fields[2], target->hours, 0, 24, error); + if (*error) goto return_res; + set_days_of_month(fields[3], target->days_of_month, error); + if (*error) goto return_res; + set_months(fields[4], target->months, error); + if (*error) goto return_res; + set_days_of_week(fields[5], target->days_of_week, error); + if (*error) goto return_res; + + goto return_res; + + return_res: + free_splitted(fields, len); +} + +time_t cron_next(cron_expr* expr, time_t date) { + /* + The plan: + + 1 Round up to the next whole second + + 2 If seconds match move on, otherwise find the next match: + 2.1 If next match is in the next minute then roll forwards + + 3 If minute matches move on, otherwise find the next match + 3.1 If next match is in the next hour then roll forwards + 3.2 Reset the seconds and go to 2 + + 4 If hour matches move on, otherwise find the next match + 4.1 If next match is in the next day then roll forwards, + 4.2 Reset the minutes and seconds and go to 2 + + ... + */ + if (!expr) return CRON_INVALID_INSTANT; + struct tm calval; + memset(&calval, 0, sizeof(struct tm)); + struct tm* calendar = cron_time(&date, &calval); + if (!calendar) return CRON_INVALID_INSTANT; + time_t original = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; + + int res = do_next(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + + time_t calculated = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; + if (calculated == original) { + /* We arrived at the original timestamp - round up to the next whole second and try again... */ + res = add_to_field(calendar, CRON_CF_SECOND, 1); + if (0 != res) return CRON_INVALID_INSTANT; + res = do_next(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + } + + return cron_mktime(calendar); +} + + +/* https://github.com/staticlibs/ccronexpr/pull/8 */ + +static unsigned int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) { + int i; + if (!bits) { + *notfound = 1; + return 0; + } + for (i = from_index; i >= to_index; i--) { + if (cron_get_bit(bits, i)) return i; + } + *notfound = 1; + return 0; +} + +static int last_day_of_month(int month, int year) { + struct tm cal; + time_t t; + memset(&cal,0,sizeof(cal)); + cal.tm_sec=0; + cal.tm_min=0; + cal.tm_hour=0; + cal.tm_mon = month+1; + cal.tm_mday = 0; + cal.tm_year=year; + t=mktime(&cal); + return gmtime(&t)->tm_mday; +} + +/** + * Reset the calendar setting all the fields provided to zero. + */ +static int reset_max(struct tm* calendar, int field) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = 59; + break; + case CRON_CF_MINUTE: + calendar->tm_min = 59; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = 23; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = 6; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year); + break; + case CRON_CF_MONTH: + calendar->tm_mon = 11; + break; + case CRON_CF_YEAR: + /* I don't think this is supposed to happen ... */ + fprintf(stderr, "reset CRON_CF_YEAR\n"); + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +static int reset_all_max(struct tm* calendar, int* fields) { + int i; + int res = 0; + if (!calendar || !fields) { + return 1; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (-1 != fields[i]) { + res = reset_max(calendar, fields[i]); + if (0 != res) return res; + } + } + return 0; +} + +/** + * Search the bits provided for the next set bit after the value provided, + * and reset the calendar. + */ +static unsigned int find_prev(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { + int notfound = 0; + int err = 0; + unsigned int next_value = prev_set_bit(bits, value, 0, ¬found); + /* roll under if needed */ + if (notfound) { + err = add_to_field(calendar, nextField, -1); + if (err) goto return_error; + err = reset_max(calendar, field); + if (err) goto return_error; + notfound = 0; + next_value = prev_set_bit(bits, max - 1, value, ¬found); + } + if (notfound || next_value != value) { + err = set_field(calendar, field, next_value); + if (err) goto return_error; + err = reset_all_max(calendar, lower_orders); + if (err) goto return_error; + } + return next_value; + + return_error: + *res_out = 1; + return 0; +} + +static unsigned int find_prev_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { + int err; + unsigned int count = 0; + unsigned int max = 366; + while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { + err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, -1); + + if (err) goto return_error; + day_of_month = calendar->tm_mday; + day_of_week = calendar->tm_wday; + reset_all_max(calendar, resets); + } + return day_of_month; + + return_error: + *res_out = 1; + return 0; +} + +static int do_prev(cron_expr* expr, struct tm* calendar, unsigned int dot) { + int i; + int res = 0; + int* resets = NULL; + int* empty_list = NULL; + unsigned int second = 0; + unsigned int update_second = 0; + unsigned int minute = 0; + unsigned int update_minute = 0; + unsigned int hour = 0; + unsigned int update_hour = 0; + unsigned int day_of_week = 0; + unsigned int day_of_month = 0; + unsigned int update_day_of_month = 0; + unsigned int month = 0; + unsigned int update_month = 0; + + resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!resets) goto return_result; + empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!empty_list) goto return_result; + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + resets[i] = -1; + empty_list[i] = -1; + } + + second = calendar->tm_sec; + update_second = find_prev(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); + if (0 != res) goto return_result; + if (second == update_second) { + push_to_fields_arr(resets, CRON_CF_SECOND); + } + + minute = calendar->tm_min; + update_minute = find_prev(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); + if (0 != res) goto return_result; + if (minute == update_minute) { + push_to_fields_arr(resets, CRON_CF_MINUTE); + } else { + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + + hour = calendar->tm_hour; + update_hour = find_prev(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); + if (0 != res) goto return_result; + if (hour == update_hour) { + push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); + } else { + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + + day_of_week = calendar->tm_wday; + day_of_month = calendar->tm_mday; + update_day_of_month = find_prev_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); + if (0 != res) goto return_result; + if (day_of_month == update_day_of_month) { + push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); + } else { + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + + month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ + update_month = find_prev(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); + if (0 != res) goto return_result; + if (month != update_month) { + if (dot - calendar->tm_year > CRON_MAX_YEARS_DIFF) { + res = -1; + goto return_result; + } + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + goto return_result; + + return_result: + if (!resets || !empty_list) { + res = -1; + } + if (resets) { + cron_free(resets); + } + if (empty_list) { + cron_free(empty_list); + } + return res; +} + +time_t cron_prev(cron_expr* expr, time_t date) { + /* + The plan: + + 1 Round down to a whole second + + 2 If seconds match move on, otherwise find the next match: + 2.1 If next match is in the next minute then roll forwards + + 3 If minute matches move on, otherwise find the next match + 3.1 If next match is in the next hour then roll forwards + 3.2 Reset the seconds and go to 2 + + 4 If hour matches move on, otherwise find the next match + 4.1 If next match is in the next day then roll forwards, + 4.2 Reset the minutes and seconds and go to 2 + + ... + */ + if (!expr) return CRON_INVALID_INSTANT; + struct tm calval; + memset(&calval, 0, sizeof(struct tm)); + struct tm* calendar = cron_time(&date, &calval); + if (!calendar) return CRON_INVALID_INSTANT; + time_t original = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; + + /* calculate the previous occurrence */ + int res = do_prev(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + + /* check for a match, try from the next second if one wasn't found */ + time_t calculated = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; + if (calculated == original) { + /* We arrived at the original timestamp - round up to the next whole second and try again... */ + res = add_to_field(calendar, CRON_CF_SECOND, -1); + if (0 != res) return CRON_INVALID_INSTANT; + res = do_prev(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + } + + return cron_mktime(calendar); +} diff --git a/src/modules/virtual/Cron/ccronexpr/ccronexpr.h b/src/modules/virtual/Cron/ccronexpr/ccronexpr.h new file mode 100644 index 00000000..8f7ad30d --- /dev/null +++ b/src/modules/virtual/Cron/ccronexpr/ccronexpr.h @@ -0,0 +1,95 @@ +/* + * Copyright 2015, alex at staticlibs.net + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * File: ccronexpr.h + * Author: alex + * + * Created on February 24, 2015, 9:35 AM + */ + +#ifndef CCRONEXPR_H +#define CCRONEXPR_H + +#define CRON_USE_LOCAL_TIME + +#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) +extern "C" { +#endif + +#ifndef ANDROID +#include +#else /* ANDROID */ +#include +#endif /* ANDROID */ + +#include /*added for use if uint*_t data types*/ + +/** + * Parsed cron expression + */ +typedef struct { + uint8_t seconds[8]; + uint8_t minutes[8]; + uint8_t hours[3]; + uint8_t days_of_week[1]; + uint8_t days_of_month[4]; + uint8_t months[2]; +} cron_expr; + +/** + * Parses specified cron expression. + * + * @param expression cron expression as nul-terminated string, + * should be no longer that 256 bytes + * @param pointer to cron expression structure, it's client code responsibility + * to free/destroy it afterwards + * @param error output error message, will be set to string literal + * error message in case of error. Will be set to NULL on success. + * The error message should NOT be freed by client. + */ +void cron_parse_expr(const char* expression, cron_expr* target, const char** error); + +/** + * Uses the specified expression to calculate the next 'fire' date after + * the specified date. All dates are processed as UTC (GMT) dates + * without timezones information. To use local dates (current system timezone) + * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' + * + * @param expr parsed cron expression to use in next date calculation + * @param date start date to start calculation from + * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. + */ +time_t cron_next(cron_expr* expr, time_t date); + +/** + * Uses the specified expression to calculate the previous 'fire' date after + * the specified date. All dates are processed as UTC (GMT) dates + * without timezones information. To use local dates (current system timezone) + * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' + * + * @param expr parsed cron expression to use in previous date calculation + * @param date start date to start calculation from + * @return previous 'fire' date in case of success, '((time_t) -1)' in case of error. + */ +time_t cron_prev(cron_expr* expr, time_t date); + + +#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) +} /* extern "C"*/ +#endif + +#endif /* CCRONEXPR_H */ diff --git a/src/modules/virtual/Cron/modinfo.json b/src/modules/virtual/Cron/modinfo.json new file mode 100644 index 00000000..c305ab4e --- /dev/null +++ b/src/modules/virtual/Cron/modinfo.json @@ -0,0 +1,57 @@ +{ + "menuSection": "Виртуальные элементы", + "configItem": [ + { + "global": 0, + "name": "Будильник (Cron)", + "type": "Writing", + "subtype": "Cron", + "id": "cron", + "widget": "anydataDef", + "page": "Таймеры", + "descr": "Будильник", + "int": 1, + "val": "*/15 * * * * *", + "formatNextAlarm": "%H:%M:%S", + "needSave": 0 + } + ], + "about": { + "authorName": "Ilya Belyakov", + "authorContact": "https://t.me/Biveraxe", + "authorGit": "https://github.com/biveraxe", + "specialThanks": "", + "moduleName": "Cron", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "Будильник типа Cron", + "moduleDesc": "Планировщик времени для периодического выполнения заданий в определённое время. Генерирует событие в указанное время по формату Cron https://ru.wikipedia.org/wiki/Cron ", + "propInfo": { + "formatNextAlarm": "Формат представления даты и времени срабатывания следующего уведомления. http://cppstudio.com/post/621/", + "needSave": "Требуется сохранять(1) или нет(0) состояние в энерго независимую память." + }, + "retInfo": "Содержит время следующего срабатывания.", + "funcInfo": [ + { + "name": "stop", + "descr": "Поставить процесс на паузу, при этом не будет событий.", + "params": [] + }, + { + "name": "continue", + "descr": "Продолжить выполнение с момента остановки.", + "params": [] + } + ] + }, + "defActive": true, + "usedLibs": { + "esp32_4mb": [], + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] + } +} \ No newline at end of file diff --git a/src/modules/virtual/Loging/Loging.cpp b/src/modules/virtual/Loging/Loging.cpp index d7649e1f..3c26c933 100644 --- a/src/modules/virtual/Loging/Loging.cpp +++ b/src/modules/virtual/Loging/Loging.cpp @@ -9,6 +9,7 @@ class Loging : public IoTItem { private: String logid; String id; + String tmpValue; String filesList = ""; int _publishType = -2; @@ -22,7 +23,7 @@ class Loging : public IoTItem { String prevDate = ""; bool firstTimeDate = true; - unsigned long interval; + long interval; public: Loging(String parameters) : IoTItem(parameters) { @@ -103,7 +104,56 @@ class Loging : public IoTItem { //запускаем процедуру удаления старых файлов если память переполняется deleteLastFile(); } +void SetDoByInterval(String valse) { + String value = valse; + //если значение логгирования пустое + if (value == "") { + SerialPrint("E", F("LogingEvent"), "'" + id + "' loging value is empty, return"); + return; + } + //если время не было получено из интернета + if (!isTimeSynch) { + SerialPrint("E", F("LogingEvent"), "'" + id + "' Сant loging - time not synchronized, return"); + return; + } + regEvent(value, F("LogingEvent")); + String logData; + jsonWriteInt(logData, "x", unixTime); + jsonWriteFloat(logData, "y1", value.toFloat()); + //прочитаем путь к файлу последнего сохранения + String filePath = readDataDB(id); + //если данные о файле отсутствуют, создадим новый + if (filePath == "failed" || filePath == "") { + SerialPrint("E", F("LogingEvent"), "'" + id + "' file path not found, start create new file"); + createNewFileWithData(logData); + return; + } else { + //если файл все же есть но был создан не сегодня, то создаем сегодняшний + if (getTodayDateDotFormated() != getDateDotFormatedFromUnix(getFileUnixLocalTime(filePath))) { + SerialPrint("E", F("LogingEvent"), "'" + id + "' file too old, start create new file"); + createNewFileWithData(logData); + return; + } + } + + //считаем количество строк и определяем размер файла + size_t size = 0; + int lines = countJsonObj(filePath, size); + SerialPrint("i", F("LogingEvent"), "'" + id + "' " + "lines = " + String(lines) + ", size = " + String(size)); + + //если количество строк до заданной величины и дата не менялась + if (lines <= points && !hasDayChanged()) { + //просто добавим в существующий файл новые данные + addNewDataToExistingFile(filePath, logData); + //если больше или поменялась дата то создадим следующий файл + } else { + createNewFileWithData(logData); + } + //запускаем процедуру удаления старых файлов если память переполняется + deleteLastFile(); + + } void createNewFileWithData(String &logData) { logData = logData + ","; String path = "/lg/" + id + "/" + String(unixTimeShort) + ".txt"; //создадим путь вида /lg/id/133256622333.txt @@ -176,13 +226,14 @@ class Loging : public IoTItem { 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) { - publishChartToWs(path, _wsNum, 1000, calculateMaxCount(), id); + 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()); - publishChartToWs(path, _wsNum, 1000, calculateMaxCount(), id); } SerialPrint("i", F("Loging"), String(f) + ") " + path + ", " + getDateTimeDotFormatedFromUnix(fileUnixTimeLocal) + ", sent"); } else { @@ -197,6 +248,24 @@ class Loging : public IoTItem { } } + String getAdditionalJson() { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; + return json; + } + + 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() { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}"; + sendStringToWs("chartb", json, -1); + } + void clearHistory() { String dir = "/lg/" + id; cleanDirectory(dir); @@ -224,21 +293,7 @@ class Loging : public IoTItem { } } - void clearValue() { - String topic = mqttRootDevice + "/" + id; - String json = "{\"maxCount\":0,\"topic\":\"" + topic + "\",\"status\":[]}"; - String pk = "/string/chart.json|" + json; - standWebSocket.broadcastTXT(pk); - } - - 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) { + void setPublishDestination(int publishType, int wsNum) { _publishType = publishType; _wsNum = wsNum; } @@ -253,12 +308,14 @@ class Loging : public IoTItem { difference = currentMillis - prevMillis; if (difference >= interval) { prevMillis = millis(); - this->doByInterval(); + if(interval != 0){ + this->doByInterval(); + } } } } - void regEvent(String value, String consoleInfo = "") { + void regEvent(const String& value, const String& consoleInfo, bool error = false, bool genEvent = true) { String userDate = getItemValue(id + "-date"); String currentDate = getTodayDateDotFormated(); //отправляем в график данные только когда выбран сегодняшний день @@ -280,6 +337,12 @@ class Loging : public IoTItem { 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", "Loging", "setValue:" + String(value.valD)); + regEvent(value.valS, "Loging", false, genEvent); + } }; void *getAPI_Loging(String subtype, String param) { @@ -301,20 +364,19 @@ class Date : public IoTItem { value.isDecimal = false; } - void setValue(String valStr) { + void setValue(const String& valStr, bool genEvent = true) { value.valS = valStr; - setValue(value); + setValue(value, genEvent); } - void setValue(IoTValue Value) { + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; - regEvent(value.valS, ""); + 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, "-")) { - (*it)->setPublishDestination(TO_MQTT_WS); - (*it)->clearValue(); + (*it)->setPublishDestination(TO_MQTT_WS, -1); (*it)->publishValue(); } } diff --git a/src/modules/virtual/Loging/modinfo.json b/src/modules/virtual/Loging/modinfo.json index 826a7b1c..53a962f7 100644 --- a/src/modules/virtual/Loging/modinfo.json +++ b/src/modules/virtual/Loging/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Виртуальные элементы", "configItem": [ { + "global": 0, "name": "График", "type": "Writing", "subtype": "Loging", @@ -13,6 +14,19 @@ "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": { diff --git a/src/modules/virtual/LogingDaily/LogingDaily.cpp b/src/modules/virtual/LogingDaily/LogingDaily.cpp index fa4d5f59..50ed724f 100644 --- a/src/modules/virtual/LogingDaily/LogingDaily.cpp +++ b/src/modules/virtual/LogingDaily/LogingDaily.cpp @@ -21,7 +21,7 @@ class LogingDaily : public IoTItem { String prevDate = ""; bool firstTimeDate = true; - unsigned long interval; + long interval; public: LogingDaily(String parameters) : IoTItem(parameters) { @@ -168,14 +168,14 @@ class LogingDaily : public IoTItem { path = "/lgd/" + id + path; f++; - + String json = getAdditionalJson(); if (_publishType == TO_MQTT) { publishChartFileToMqtt(path, id, calculateMaxCount()); } else if (_publishType == TO_WS) { - publishChartToWs(path, _wsNum, 1000, calculateMaxCount(), id); + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); } else if (_publishType == TO_MQTT_WS) { publishChartFileToMqtt(path, id, calculateMaxCount()); - publishChartToWs(path, _wsNum, 1000, calculateMaxCount(), id); + sendFileToWsByFrames(path, "charta", json, _wsNum, WEB_SOCKETS_FRAME_SIZE); } SerialPrint("i", F("LogingDaily"), String(f) + ") " + path + ", sent"); @@ -183,17 +183,23 @@ class LogingDaily : public IoTItem { } } + String getAdditionalJson() { + String topic = mqttRootDevice + "/" + id; + String json = "{\"maxCount\":" + String(calculateMaxCount()) + ",\"topic\":\"" + topic + "\"}"; + return json; + } + void clearHistory() { String dir = "/lgd/" + 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 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; diff --git a/src/modules/virtual/LogingDaily/modinfo.json b/src/modules/virtual/LogingDaily/modinfo.json index 6e5827bd..45623902 100644 --- a/src/modules/virtual/LogingDaily/modinfo.json +++ b/src/modules/virtual/LogingDaily/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Виртуальные элементы", "configItem": [ { + "global": 0, "name": "График дневного расхода", "type": "Writing", "subtype": "LogingDaily", @@ -13,7 +14,7 @@ "int": 1, "logid": "t", "points": 365, - "test": 0 + "column": 0 } ], "about": { @@ -33,7 +34,7 @@ "int": "Интервал логирования в мнутах, частота проверки смены суток в минутах. Не рекомендуется менять", "logid": "ID накопительной величины которую будем логировать", "points": "Максимальное количество точек", - "test": "Режим тестирования - график будет обновляться не раз в сутки, а кадый заданный в int интервал" + "column": "Режим тестирования - график будет обновляться не раз в сутки, а кадый заданный в int интервал. Суточные столбики - 0, Минутные столбики - 1" } }, "defActive": true, diff --git a/src/modules/virtual/Timer/Timer.cpp b/src/modules/virtual/Timer/Timer.cpp index e5cc7039..822e8110 100644 --- a/src/modules/virtual/Timer/Timer.cpp +++ b/src/modules/virtual/Timer/Timer.cpp @@ -9,7 +9,6 @@ class Timer : public IoTItem { bool _unfin = false; bool _ticker = false; bool _repeat = false; - bool _needSave = false; bool _pause = false; int _initValue; @@ -17,23 +16,24 @@ class Timer : public IoTItem { Timer(String parameters): IoTItem(parameters) { jsonRead(parameters, "countDown", _initValue); _unfin = !_initValue; - value.valD = _initValue; - if (_initValue) value.valD = value.valD + 1; // +1 - компенсируем ранний вычет счетчика, ранний вычет, чтоб после события значение таймера не исказилось. - // +0 - если изначально установили бесконечный счет + + if (!_needSave) { + value.valD = _initValue; + if (_initValue) value.valD = value.valD + 1; // +1 - компенсируем ранний вычет счетчика, ранний вычет, чтоб после события значение таймера не исказилось. + } jsonRead(parameters, "ticker", _ticker); jsonRead(parameters, "repeat", _repeat); - jsonRead(parameters, "needSave", _needSave); // нужно сохранять счетчик в постоянную память } void doByInterval() { if (!_unfin && value.valD >= 0 && !_pause) { if (_repeat && value.valD == 0) value.valD = _initValue; value.valD--; - if (_needSave) needSave = true; if (value.valD == 0) { regEvent(value.valD, "Time's up"); } + //if (!_ticker) regEvent(getValue(), "Timer tick", false, false); // только регистрируем изменения без генерации тиков } if (_ticker && (value.valD > 0 || _unfin) && !_pause) regEvent(value.valD, "Timer tick"); diff --git a/src/modules/virtual/Timer/modinfo.json b/src/modules/virtual/Timer/modinfo.json index a388be9f..e9379c50 100644 --- a/src/modules/virtual/Timer/modinfo.json +++ b/src/modules/virtual/Timer/modinfo.json @@ -2,6 +2,7 @@ "menuSection": "Виртуальные элементы", "configItem": [ { + "global": 0, "name": "Таймер", "type": "Writing", "subtype": "Timer", @@ -69,6 +70,8 @@ "defActive": true, "usedLibs": { "esp32_4mb": [], - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } \ No newline at end of file diff --git a/src/modules/virtual/VButton/VButton.cpp b/src/modules/virtual/VButton/VButton.cpp index 2bd19cb8..59d0118a 100644 --- a/src/modules/virtual/VButton/VButton.cpp +++ b/src/modules/virtual/VButton/VButton.cpp @@ -4,11 +4,13 @@ class VButton : public IoTItem { public: - VButton(String parameters): IoTItem(parameters) { } + VButton(String parameters): IoTItem(parameters) { + _round = 0; + } - void setValue(IoTValue Value) { + void setValue(const IoTValue& Value, bool genEvent = true) { value = Value; - regEvent((String)(int)value.valD, "VButton"); + regEvent((String)(int)value.valD, "VButton", false, genEvent); } String getValue() { diff --git a/src/modules/virtual/VButton/modinfo.json b/src/modules/virtual/VButton/modinfo.json index fd1811b7..05c5dcd9 100644 --- a/src/modules/virtual/VButton/modinfo.json +++ b/src/modules/virtual/VButton/modinfo.json @@ -2,10 +2,12 @@ "menuSection": "Виртуальные элементы", "configItem": [ { + "global": 0, "name": "Виртуальная кнопка", "type": "Reading", "subtype": "VButton", "id": "vbtn", + "needSave": 0, "widget": "toggle", "page": "Кнопки", "descr": "Кнопка", @@ -34,6 +36,8 @@ "defActive": true, "usedLibs": { "esp32_4mb": [], - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } \ No newline at end of file diff --git a/src/modules/virtual/Variable/Variable.cpp b/src/modules/virtual/Variable/Variable.cpp index c921bb64..755506a4 100644 --- a/src/modules/virtual/Variable/Variable.cpp +++ b/src/modules/virtual/Variable/Variable.cpp @@ -8,11 +8,6 @@ class Variable : public IoTItem { Variable(String parameters) : IoTItem(parameters) { } - // особенность данного модуля - просто хранение значения для сценария, нет событий - // void setValue(IoTValue Value) { - // value = Value; - // } - void doByInterval() { } }; diff --git a/src/modules/virtual/Variable/modinfo.json b/src/modules/virtual/Variable/modinfo.json index 9aec85e8..85f957b4 100644 --- a/src/modules/virtual/Variable/modinfo.json +++ b/src/modules/virtual/Variable/modinfo.json @@ -2,52 +2,60 @@ "menuSection": "Виртуальные элементы", "configItem": [ { + "global": 0, "name": "Окно ввода числа (переменная)", "type": "Reading", "subtype": "Variable", "id": "value", + "needSave": 0, "widget": "inputDgt", "page": "Ввод", "descr": "Введите число", "int": "0", "val": "0.0", - "num": 2 + "map": "1024,1024,1,100", + "plus": 0, + "multiply": 1, + "round": 0 }, { + "global": 0, "name": "Окно ввода времени", "type": "Reading", "subtype": "Variable", "id": "time", + "needSave": 0, "widget": "inputTm", "page": "Ввод", "descr": "Введите время", "int": "0", - "val": "02:00", - "num": 3 + "val": "02:00" }, { + "global": 0, "name": "Окно ввода даты", "type": "Reading", "subtype": "Variable", "id": "time", + "needSave": 0, "widget": "inputDate", "page": "Ввод", "descr": "Введите дату", "int": "0", - "val": "24.05.2022", - "num": 4 + "val": "24.05.2022" }, { + "global": 0, "name": "Окно ввода текста", "type": "Reading", "subtype": "Variable", "id": "txt", + "needSave": 0, "widget": "inputTxt", "page": "Ввод", "descr": "Введите текст", "int": "0", - "val": "текст", - "num": 5 + "val": "текст" } ], "about": { @@ -65,12 +73,14 @@ "moduleDesc": "Специальный системный модуль для использования переменных в процессе автоматизации как элементов конфигурации.", "propInfo": { "int": "Не используется", - "val": "Не используется" + "val": "Значение при старте" } }, "defActive": true, "usedLibs": { "esp32_4mb": [], - "esp8266_4mb": [] + "esp8266_4mb": [], + "esp8266_1mb": [], + "esp8266_1mb_ota": [] } } \ No newline at end of file diff --git a/src/utils/FileUtils.cpp b/src/utils/FileUtils.cpp index f6c128db..d40ab4d2 100644 --- a/src/utils/FileUtils.cpp +++ b/src/utils/FileUtils.cpp @@ -329,10 +329,10 @@ String createDataBaseSting() { for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { if ((*it)->getSubtype() == "LogingDaily") { String id = (*it)->getID(); - id = "/lgd/" + id + "/" + id + ".txt"; - String fileContent = readFile(id, 10000); + String path = "/lgd/" + id + "/" + id + ".txt"; + String fileContent = readFile(path, 10000); if (fileContent == "failed") { - SerialPrint("i", "Export", "file not exist " + id); + SerialPrint("i", "Export", "file not exist " + path); } else { out += "=>" + fileContent + "\r\n"; } diff --git a/src/utils/I2CUtils.cpp b/src/utils/I2CUtils.cpp index 9d3f9032..a8c643c6 100644 --- a/src/utils/I2CUtils.cpp +++ b/src/utils/I2CUtils.cpp @@ -4,32 +4,29 @@ void scanI2C() { byte error, address; int nDevices; - - Serial.println("Scanning..."); + String message = ""; nDevices = 0; - for(address = 8; address < 127; address++ ){ + for(address = 8; address < 127; address++ ) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0){ - Serial.print("I2C device found at address 0x"); - if (address<16) - Serial.print("0"); - Serial.print(address,HEX); - Serial.println(" !"); + message += "I2C device found at address 0x"; + message += uint64ToString(address, 16); + message += " !"; nDevices++; } else if (error==4) { - Serial.print("Unknow error at address 0x"); - if (address<16) - Serial.print("0"); - Serial.println(address,HEX); + message += "Unknow error at address 0x"; + message += uint64ToString(address, 16); } } if (nDevices == 0) - Serial.println("No I2C devices found\n"); + message += "No I2C devices found\n"; else - Serial.println("done\n"); + message += "done\n"; + + SerialPrint("i", "I2C Scaner", message); } \ No newline at end of file diff --git a/src/utils/JsonUtils.cpp b/src/utils/JsonUtils.cpp index f6da97af..f04f910f 100644 --- a/src/utils/JsonUtils.cpp +++ b/src/utils/JsonUtils.cpp @@ -11,119 +11,101 @@ void jsonWriteStrDoc(DynamicJsonDocument& doc, String name, String value) { } // new============================================================================== -bool jsonRead(String& json, String key, unsigned long& value, bool e) { - bool ret = true; +bool jsonRead(const String& json, String key, long& value, bool e) { DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); + SerialPrint("E", F("jsonRead"), error.f_str()); jsonErrorDetected(); } - ret = false; + return false; } else if (!doc.containsKey(key)) { if (e) { - SerialPrint("EE", F("jsonRead"), "json key '" + key + "' missing"); + SerialPrint("E", F("jsonRead"), key + " missing"); jsonErrorDetected(); } - ret = false; + return false; } - value = doc[key].as(); - return ret; + value = doc[key].as(); + return true; } -bool jsonRead(String& json, String key, float& value, bool e) { - bool ret = true; +bool jsonRead(const String& json, String key, float& value, bool e) { DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); + SerialPrint("E", F("jsonRead"), error.f_str()); jsonErrorDetected(); } - ret = false; + return false; } else if (!doc.containsKey(key)) { if (e) { - SerialPrint("EE", F("jsonRead"), key + " missing"); + SerialPrint("E", F("jsonRead"), key + " missing"); jsonErrorDetected(); } - ret = false; + return false; } value = doc[key].as(); - return ret; + return true; } -bool jsonRead(String& json, String key, String& value, bool e) { - bool ret = true; +bool jsonRead(const String& json, String key, String& value, bool e) { DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); + SerialPrint("E", F("jsonRead"), error.f_str()); jsonErrorDetected(); } - ret = false; + return false; } else if (!doc.containsKey(key)) { if (e) { - SerialPrint("EE", F("jsonRead"), key + " missing"); + SerialPrint("E", F("jsonRead"), key + " missing"); jsonErrorDetected(); } - ret = false; + return false; } value = doc[key].as(); + return true; +} + +bool jsonRead(const String& json, String key, bool& value, bool e) { + int lvalue = value; + bool ret = jsonRead(json, key, lvalue, e); + value = lvalue; return ret; } -bool jsonRead(String& json, String key, bool& value, bool e) { - bool ret = true; +bool jsonRead(const String& json, String key, int& value, bool e) { DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); + SerialPrint("E", F("jsonRead"), error.f_str()); jsonErrorDetected(); } - ret = false; + return false; } else if (!doc.containsKey(key)) { if (e) { - SerialPrint("EE", F("jsonRead"), key + " missing"); + SerialPrint("E", F("jsonRead"), key + " missing"); jsonErrorDetected(); } - ret = false; + return false; } value = doc[key].as(); - return ret; -} - -bool jsonRead(String& json, String key, int& value, bool e) { - bool ret = true; - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - DeserializationError error = deserializeJson(doc, json); - if (error) { - if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); - jsonErrorDetected(); - } - ret = false; - } else if (!doc.containsKey(key)) { - if (e) { - SerialPrint("EE", F("jsonRead"), key + " missing"); - jsonErrorDetected(); - } - ret = false; - } - value = doc[key].as(); - return ret; + return true; } // new============================================================================== -bool jsonWriteStr_(String& json, String key, String value, bool e) { +bool jsonWriteStr_(String& json, const String& key, const String& value, bool e) { bool ret = true; DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } ret = false; @@ -134,13 +116,13 @@ bool jsonWriteStr_(String& json, String key, String value, bool e) { return ret; } -bool jsonWriteBool_(String& json, String key, bool value, bool e) { +bool jsonWriteBool_(String& json, const String& key, bool value, bool e) { bool ret = true; DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } ret = false; @@ -151,13 +133,13 @@ bool jsonWriteBool_(String& json, String key, bool value, bool e) { return ret; } -bool jsonWriteInt_(String& json, String key, int value, bool e) { +bool jsonWriteInt_(String& json, const String& key, int value, bool e) { bool ret = true; DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } ret = false; @@ -168,13 +150,13 @@ bool jsonWriteInt_(String& json, String key, int value, bool e) { return ret; } -bool jsonWriteFloat_(String& json, String key, float value, bool e) { +bool jsonWriteFloat_(String& json, const String &key, float value, bool e) { bool ret = true; DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } ret = false; @@ -203,7 +185,7 @@ bool jsonMergeObjects(String& json1, String& json2, bool e) { jsonMergeDocs(doc1.as(), doc2.as()); if (error1 || error2) { if (e) { - SerialPrint("EE", F("json"), "jsonMergeObjects error"); + SerialPrint("E", F("json"), "jsonMergeObjects error"); jsonErrorDetected(); } ret = false; @@ -220,36 +202,36 @@ void jsonMergeDocs(JsonObject dest, JsonObjectConst src) { } // depricated====================================================================== -String jsonReadStr(String& json, String name, bool e) { +String jsonReadStr(const String& json, String name, bool e) { DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); + SerialPrint("E", F("jsonRead"), error.f_str()); jsonErrorDetected(); } } return doc[name].as(); } -boolean jsonReadBool(String& json, String name, bool e) { +boolean jsonReadBool(const String& json, String name, bool e) { DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); + SerialPrint("E", F("jsonRead"), error.f_str()); jsonErrorDetected(); } } return doc[name].as(); } -int jsonReadInt(String& json, String name, bool e) { +int jsonReadInt(const String& json, String name, bool e) { DynamicJsonDocument doc(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonRead"), error.f_str()); + SerialPrint("E", F("jsonRead"), error.f_str()); jsonErrorDetected(); } } @@ -262,7 +244,7 @@ String jsonWriteStr(String& json, String name, String value, bool e) { DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } } @@ -277,7 +259,7 @@ String jsonWriteBool(String& json, String name, boolean value, bool e) { DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } } @@ -292,7 +274,7 @@ String jsonWriteInt(String& json, String name, int value, bool e) { DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } } @@ -307,7 +289,7 @@ String jsonWriteFloat(String& json, String name, float value, bool e) { DeserializationError error = deserializeJson(doc, json); if (error) { if (e) { - SerialPrint("EE", F("jsonWrite"), error.f_str()); + SerialPrint("E", F("jsonWrite"), error.f_str()); jsonErrorDetected(); } } @@ -318,8 +300,9 @@ String jsonWriteFloat(String& json, String name, float value, bool e) { } void jsonErrorDetected() { - jsonWriteInt(errorsHeapJson, F("jse2"), 1); - int number = jsonReadInt(errorsHeapJson, F("jse2n")); - number++; - jsonWriteInt(errorsHeapJson, F("jse2n"), number); + // пример как отправить ошибку с количеством + // jsonWriteInt(errorsHeapJson, F("jse2"), 1); + // int number = jsonReadInt(errorsHeapJson, F("jse2n")); + // number++; + // jsonWriteInt(errorsHeapJson, F("jse2n"), number); } \ No newline at end of file diff --git a/src/utils/SerialPrint.cpp b/src/utils/SerialPrint.cpp index ce713c12..d057beb3 100644 --- a/src/utils/SerialPrint.cpp +++ b/src/utils/SerialPrint.cpp @@ -1,20 +1,29 @@ #include "utils/SerialPrint.h" -void SerialPrint(String errorLevel, String module, String msg) { - String tosend; - - tosend = prettyMillis(millis()); - - // if (module == "Loging") { - tosend = tosend + " [" + errorLevel + "] [" + module + "] " + msg; +void SerialPrint(const String& errorLevel, const String& module, const String& msg, const String& itemId) { + String tosend = prettyMillis(millis()); + tosend += " ["; + tosend += errorLevel; + tosend += "] ["; + tosend += module; + tosend += "] "; + tosend += msg; Serial.println(tosend); if (isNetworkActive()) { if (jsonReadInt(settingsFlashJson, F("log")) != 0) { - String pl = "/log|" + tosend; - standWebSocket.broadcastTXT(pl); + sendStringToWs("corelg", tosend, -1); } } - //} + + // if (errorLevel == "E") { + // cleanString(tosend); + // // создаем событие об ошибке для возможной реакции в сценарии + // if (itemId != "") { + // createItemFromNet(itemId + "_onError", tosend, -4); + // } else { + // createItemFromNet("onError", tosend, -4); + // } + // } } \ No newline at end of file diff --git a/src/utils/StringUtils.cpp b/src/utils/StringUtils.cpp index 0a2b06ac..4234a68f 100644 --- a/src/utils/StringUtils.cpp +++ b/src/utils/StringUtils.cpp @@ -8,12 +8,12 @@ void writeUint8tToString(uint8_t* payload, size_t length, size_t headerLenth, St } } -String selectToMarkerLast(String str, String found) { +String selectToMarkerLast(String str, const String& found) { int p = str.lastIndexOf(found); return str.substring(p + found.length()); } -String selectToMarker(String str, String found) { +String selectToMarker(String str, const String& found) { int p = str.indexOf(found); return str.substring(0, p); } @@ -24,32 +24,32 @@ String extractInner(String str) { return str.substring(p1 + 1, p2); } -String deleteAfterDelimiter(String str, String found) { +String deleteAfterDelimiter(String str, const String& found) { int p = str.indexOf(found); return str.substring(0, p); } -String deleteBeforeDelimiter(String str, String found) { +String deleteBeforeDelimiter(String str, const String& found) { int p = str.indexOf(found) + found.length(); return str.substring(p); } -String deleteBeforeDelimiterTo(String str, String found) { +String deleteBeforeDelimiterTo(String str, const String& found) { int p = str.indexOf(found); return str.substring(p); } -String deleteToMarkerLast(String str, String found) { +String deleteToMarkerLast(String str, const String& found) { int p = str.lastIndexOf(found); return str.substring(0, p); } -String selectToMarkerPlus(String str, String found, int plus) { +String selectToMarkerPlus(String str, const String& found, int plus) { int p = str.indexOf(found); return str.substring(0, p + plus); } -String selectFromMarkerToMarker(String str, String tofind, int number) { +String selectFromMarkerToMarker(String str, const String& tofind, int number) { if (str.indexOf(tofind) == -1) { return "not found"; } @@ -79,7 +79,7 @@ void hex2string(byte array[], unsigned int len, char buffer[]) { buffer[len * 2] = '\0'; } -inline unsigned char ChartoHex(char ch) { +unsigned char ChartoHex(char ch) { return ((ch >= 'A') ? (ch - 'A' + 0xA) : (ch - '0')) & 0x0F; } @@ -98,14 +98,14 @@ int string2hex(const char* str, unsigned char* bytes) { return i; } -uint8_t hexStringToUint8(String hex) { +uint8_t hexStringToUint8(const String& hex) { uint8_t tmp = strtol(hex.c_str(), NULL, 0); if (tmp >= 0x00 && tmp <= 0xFF) { return tmp; } } -uint16_t hexStringToUint16(String hex) { +uint16_t hexStringToUint16(const String& hex) { uint16_t tmp = strtol(hex.c_str(), NULL, 0); if (tmp >= 0x0000 && tmp <= 0xFFFF) { return tmp; @@ -128,20 +128,20 @@ size_t itemsCount2(String str, const String& separator) { return cnt; } -size_t itemsCount(String& str, const char* delim) { - size_t cnt = 0; - char* cstr = new char[str.length() + 1]; - strcpy(cstr, str.c_str()); - char* token; - while ((token = strtok_r(cstr, delim, &cstr))) { - cnt++; - // printf("%s\n", token); - } - delete[] cstr; - return cnt; -} +// size_t itemsCount(String& str, const char* delim) { +// size_t cnt = 0; +// char* cstr = new char[str.length() + 1]; +// strcpy(cstr, str.c_str()); +// char* token; +// while ((token = strtok_r(cstr, delim, &cstr))) { +// cnt++; +// // printf("%s\n", token); +// } +// delete[] cstr; +// return cnt; +// } -char* stringToChar(String& str) { +char* stringToChar(const String& str) { char* mychar = new char[str.length() + 1]; strcpy(mychar, str.c_str()); return mychar; @@ -183,9 +183,8 @@ String prettyBytes(size_t size) { return String(size / 1024.0 / 1024.0 / 1024.0) + "GB"; } -String uint64ToString(uint64_t input) { +String uint64ToString(uint64_t input, uint8_t base) { String result = ""; - uint8_t base = 10; do { char c = input % base; @@ -199,3 +198,10 @@ String uint64ToString(uint64_t input) { } while (input); return result; } + +void cleanString(String& str) { + const String allowedChars = F("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя.!-+ "); + for (size_t i = 0; i < str.length(); i++) { + if (allowedChars.indexOf(str.charAt(i)) == -1) str.setCharAt(i, ' '); + } +} \ No newline at end of file