├── .clang-format ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .markdownlint.config ├── .travis.yml ├── LICENSE ├── README.md ├── calc-light-dimcurve ├── .gitignore ├── README.md ├── index.js ├── package.json └── yarn.lock ├── doc ├── .gitkeep ├── PCA9685-module.webp ├── esp32_devkit_v1.webp ├── pcb_2.0_1024.webp ├── pcb_2.0_2048.webp ├── pcb_20_soldered_1024.webp ├── pcb_20_soldered_2048.webp ├── prototype_on_breadboard_2048.webp ├── prototype_on_breadboard_full.webp ├── simple-diagram.graphml ├── simple-diagram.svg ├── simple-schematic.graphml └── simple-schematic.svg ├── frontend ├── .gitignore ├── .idea │ └── jsLibraryMappings.xml ├── README.md ├── gulpfile.js ├── package.json ├── src │ ├── 0050_utils.js │ ├── 0100_sliders.js │ ├── 0200_websocketclient.js │ ├── 0900_app.js │ ├── index.html │ └── index.scss ├── vendor │ ├── 100_jquery-3.4.1.js │ ├── 200_jquery.rsSliderLens.css │ └── 200_jquery.rsSliderLens.js └── yarn.lock ├── partitions_custom.csv ├── platformio.ini ├── src ├── app.cpp ├── config │ ├── config.cpp │ ├── config.h │ ├── configserver.cpp │ ├── configserver.h │ └── configserver_menu.json ├── hardware │ ├── fm24c04.cpp │ ├── fm24c04.h │ ├── fram.cpp │ ├── fram.h │ ├── pwm.cpp │ ├── pwm.h │ ├── statusled.cpp │ └── statusled.h ├── net │ ├── artnet.cpp │ ├── artnet.h │ ├── mqtt.cpp │ ├── mqtt.h │ ├── ota.cpp │ ├── ota.h │ ├── reconnector.cpp │ ├── reconnector.h │ ├── websocket.cpp │ ├── websocket.h │ ├── wifistate.cpp │ └── wifistate.h ├── state.cpp ├── state.h ├── statistic.cpp ├── statistic.h ├── util │ ├── color.h │ ├── dimcurve.h │ ├── jsonparser.cpp │ ├── jsonparser.h │ ├── logger.cpp │ ├── logger.h │ ├── multitimer.cpp │ ├── multitimer.h │ └── utils.h └── webapp │ └── index.html.gz ├── test-artnet-sender ├── .gitignore ├── README.md ├── index.artnet.js ├── index.e131.js ├── package.json └── yarn.lock ├── test-ws-client └── index.html └── test-ws-server ├── .gitignore ├── index.html ├── index.js ├── package.json └── yarn.lock /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | ColumnLimit: 140 -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: Main 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: Setup Python 12 | uses: actions/setup-python@master 13 | with: 14 | python-version: '3.x' 15 | - name: Install Platform IO 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install -U platformio 19 | - name: Build 20 | run: platformio run 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .pio 3 | .vscode 4 | 5 | src/webapp 6 | -------------------------------------------------------------------------------- /.markdownlint.config: -------------------------------------------------------------------------------- 1 | { 2 | "MD033": false 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1. ESP32 LED Dimmer 2 | 3 | ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/zebrajaeger/esp32-led-dimmer/CI) 4 | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) 5 | ![PlatformIO](https://img.shields.io/badge/PlatformIO-Community-orange.svg) 6 | 7 | 8 | 9 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 10 | 11 | - [1.1. Introducion](#11-introducion) 12 | - [1.2. Simplified Diagram](#12-simplified-diagram) 13 | - [1.3. Simplified Schematic](#13-simplified-schematic) 14 | - [1.4. Motivation](#14-motivation) 15 | - [1.5. TODO](#15-todo) 16 | - [1.5.1. Software](#151-software) 17 | - [1.5.1.1. Features](#1511-features) 18 | - [1.5.1.2. Bugs](#1512-bugs) 19 | - [1.5.2. Hardware](#152-hardware) 20 | - [1.6. Documentation](#16-documentation) 21 | - [1.6.1. Installation](#161-installation) 22 | - [1.6.2. Debug via JTAG](#162-debug-via-jtag) 23 | - [1.6.3. Storage](#163-storage) 24 | - [1.6.3.1. Rarely written](#1631-rarely-written) 25 | - [1.6.3.2. Often written](#1632-often-written) 26 | - [1.6.4. Software](#164-software) 27 | - [1.6.4.1. Configuration](#1641-configuration) 28 | - [1.6.4.2. MQTT](#1642-mqtt) 29 | - [1.6.5. Hardware 1 / schematic](#165-hardware-1--schematic) 30 | - [1.6.5.1. µC](#1651-%C2%B5c) 31 | - [1.6.5.2. Mosfets](#1652-mosfets) 32 | - [1.6.5.3. FRAM](#1653-fram) 33 | - [1.6.5.4. PCA9685](#1654-pca9685) 34 | - [1.6.5.5. DC/DC](#1655-dcdc) 35 | - [1.6.6. Hardware 2 (external components)](#166-hardware-2-external-components) 36 | - [1.6.6.1. AC/DC](#1661-acdc) 37 | - [1.6.6.2. LED stripes](#1662-led-stripes) 38 | - [1.7. Tools I used for development](#17-tools-i-used-for-development) 39 | - [1.7.1. Software](#171-software) 40 | - [1.7.2. Hardware](#172-hardware) 41 | - [1.7.3. Extended Docs](#173-extended-docs) 42 | - [1.8. Example (My implementation)](#18-example-my-implementation) 43 | - [1.8.1. Prototype](#181-prototype) 44 | - [1.8.2. Freshly produces PCB](#182-freshly-produces-pcb) 45 | - [1.8.3. Soldered PCB](#183-soldered-pcb) 46 | - [1.8.3.1. Known bugs](#1831-known-bugs) 47 | 48 | 49 | 50 | ## 1.1. Introducion 51 | 52 | This thing is to connect a LED-Lamp (with up to 16 channels and 4096 steps resolution per channel) to a mqtt server 53 | 54 | - ESP32 as µC 55 | - PCA9685 as PWM device 56 | - Frequency of PWM between 25..1500Hz configurable 57 | - 4096 Brightness values to implement a good mapping for brightness curves 58 | - FRAM MB85RC16 with 10^12 erase/write cycles for values 59 | - Different mosfets possible (low gate volatge ist required because we working with 3,3V) 60 | - Configuration is stored in ESP32 flash wich has 100.000 erase/write cycles 61 | - MQTT control 62 | - Keeps brightness value between power cycles 63 | - Mostly fast connections to WiFi net (sometimes not, maybe a hardware/driver bug) 64 | - If no WiFi connaction can etablished, it opens after 30 seconds a WiFi accesspoint to connect via Smartphone/Notebook to configure connections and credentials 65 | - Simple schematic - everything is connected via I²C bus 66 | - Made to power on and off, i.e. for a updated light with an oldscool light switch 67 | 68 | ## 1.2. Simplified Diagram 69 | 70 | ![Simple diagram](./doc/simple-diagram.svg) 71 | 72 | ## 1.3. Simplified Schematic 73 | 74 | *You can find the complete schematic (and PCB) here:* 75 | 76 | ![Simple schematic](./doc/simple-schematic.svg) 77 | 78 | ## 1.4. Motivation 79 | 80 | I tried some of these configurable projects (EasyESP, Tasmota, ESPHome, ...) but every solution of this list 81 | has big disadvantages like crashes, very slow wifi connection, no way to store the light brightnes values 82 | through power-cycles. 83 | At the end of this frustrating way I decided to write a software that matches (hopefully) all my requirements 84 | 85 | ## 1.5. TODO 86 | 87 | ### 1.5.1. Software 88 | 89 | #### 1.5.1.1. Features 90 | 91 | - [ ] Add new Images. 92 | - 'Homepage' and logo for embedded server, Diode and Poti or something like that. 93 | - [ ] Logo. 94 | - [X] Webapp backend. 95 | - [X] Webapp frontend. 96 | - [ ] Connection indicator. 97 | - [ ] Trigger WS-Server on status change. 98 | - [ ] Documentation (screenshots). 99 | - Smart Config [Expressiv doc](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/network/esp_smartconfig.html), [Google](https://lmgtfy.com/?q=esp32+smartconfig), [Other](https://www.switchdoc.com/2018/06/tutorial-esp32-bc24-provisioning-for-wifi/) 100 | - Art-Net (). 101 | - [X] Implementation. 102 | - [ ] Configuration. 103 | - [ ] Documentation. 104 | - MQTT 105 | - [ ] Port configuration. 106 | - [ ] Send username, password and port on mqtt connection. 107 | - Timer. 108 | - [X] Implementation. 109 | - [X] for periodic status updates. 110 | - [ ] Configuration of Status Update period. 111 | - [X] ~~~SSL Connections~~~ (possible?) Not possible. 112 | - Dimming between different light-values with given time. 113 | - [ ] Linear (simple). 114 | - [ ] S-curve or log or something like that - what looks nice and smooth. 115 | 116 | #### 1.5.1.2. Bugs 117 | 118 | - [X] Fix Setting Trouble (UI Update). 119 | - The Reconnector sometimes makes MQTT reconnects but maybe the underlaying TCP-Stack sends a wrong state. 120 | - [ ] Analyze. 121 | - [ ] Fix. 122 | - Every 2nd boot Connection trouble analyze 123 | - [X] Analyze. 124 | - [X] Fix. Hack -> see https://github.com/espressif/arduino-esp32/issues/2501 125 | - [X] ~~~CSS tuning~~~. No access to the page generators CSS. 126 | - [ ] Picture from an undestroyed ESP Node.... 127 | 128 | ### 1.5.2. Hardware 129 | 130 | - [X] Make PCB. 131 | - [X] With consider of jtag and reset + flash-button. 132 | - [ ] 2nd Status LED (another Color). 133 | 134 | ## 1.6. Documentation 135 | 136 | ### 1.6.1. Installation 137 | 138 | - Install platformio as described here: . 139 | - On the PIO Home tab within Visual Studio Code, go to the left toolbar and choose 'Platforms' 140 | - Install 'Expressif 32' platform 141 | - Clone this project into a directory of your choice or download it as a zip-file from here: (same link as 'Clone or download' -> 'Download ZIP'). 142 | - In the PIO Home tab choode 'Open Project' and choose the folder with the project files. 143 | 144 | - Edit the platformio.ini and change the line 145 | 146 | upload_port = COM4 147 | to the serial port where youre device is connected to. 148 | 149 | - Click on the 'Alien head' icon on the left toolbar. the 'Project Tasks' List should be visible now. 150 | 151 | 1. Not needed anymore ~~~Execute 'Upload File System Image'. This uploads the file-system-stuff.~~~ 152 | 1. Execute 'Upload'. this uploads the firmware itself. 153 | 154 | - If you don't have, change the platformio.ini file and change the 'monitor_port' line with your device port. Connect with the PlatformIO Serial Monitor (the plug icon at the bottom toolbar). 155 | 156 | - Because there are no WiFI credentials set, after a timeout of around 30s the device will spawn a accespoint you connect to with your Notebook or Smartphone. The name of the SSID starts with 'ESP' 157 | 158 | ### 1.6.2. Debug via JTAG 159 | 160 | - Olimex JTAG debugger 161 | - Change in 162 | 163 | /.platformio/packages/tool-openocd-esp32/share/openocd/scripts/board/esp-wroom-32.cfg 164 | the value of adapter_khz to 1MHz (or higher if it works stable): 165 | 166 | adapter_khz 1000 167 | 168 | ### 1.6.3. Storage 169 | 170 | #### 1.6.3.1. Rarely written 171 | 172 | We have two types of storage for data. 173 | the first one is the flash which have a livetime of 100.000 erase/program cycles and is used for rarely written data: 174 | 175 | - Firmware 176 | - Configuration data from web-interface 177 | 178 | #### 1.6.3.2. Often written 179 | 180 | The second is a small FRAM, the the MB85RC16 (I²C, 3.3V) () 181 | which can be written 10^12 times per Byte. It is used to store the brightness values which may be changed very often. 182 | 183 | ### 1.6.4. Software 184 | 185 | // TODO Only as code today, sorry 186 | 187 | #### 1.6.4.1. Configuration 188 | 189 | #### 1.6.4.2. MQTT 190 | 191 | Dictionary: 192 | 193 | - 'f': frequency: 24...1500 (in Hz) 194 | - 'd': data 195 | - 'c': channel index (starts with 0) 196 | - 'v': brightness value: 0...4096 as linear PWM brightness steps or a percentage value as string, i.E. "35.7%". Can be a simple value or an array. Starts with channel index. 197 | 198 | There are three topics: 199 | 200 | - <TopicPrefix>/alive 201 | - Device publishes 'true' after connection 202 | - On timeout the last will message is 'false' 203 | - <TopicPrefix>/status 204 | - After startup and change settings the device will publish a json status. Also every 5 min. Example (formated): 205 | ```json 206 | { 207 | "device": { 208 | "name": "esp32-30aea485fe54", 209 | "ip": "192.168.178.60" 210 | }, 211 | "light": { 212 | "frequency": 1500, 213 | "data": { 214 | "channel": 0, 215 | "value": [50,500,50,500,50,500,50,500,50,500,50,500,50,500,50,500] 216 | } 217 | } 218 | } 219 | ``` 220 | - <TopicPrefix>/set (channel values) 221 | - frequency is optional 222 | - data is optional 223 | - channel is the zero based index where the value starts 224 | - value can be a number(0...4096), a string(0.0%...100.0%) or an array with number(s) and/or string(s) 225 | ```json 226 | { 227 | "frequency": 1500, 228 | "data": { 229 | "channel": 0, 230 | "value": [50,500,50,500,50,500,50,500,50,500,50,500,50,500,50,500] 231 | } 232 | } 233 | ``` 234 | - <TopicPrefix>/set (patterns) 235 | - frequency is optional 236 | - data is optional 237 | - value can be a number(0...4096), a string(0.0%...100.0%) or an array with number(s) and/or string(s) 238 | ```json 239 | { 240 | "frequency": 1500, 241 | "data": { 242 | "all" 243 | "all": [0,500,4095] 244 | } 245 | } 246 | ``` 247 | 248 | ### 1.6.5. Hardware 1 / schematic 249 | 250 | I made the scheamtic and pcb with EASYEDA from JLPCB. A public link to this project is here: 251 | 252 | #### 1.6.5.1. µC 253 | 254 | - ESP32 (ESP32-WROOM-32) 255 | - Dual core, 240MHz 256 | - Datasheet: 257 | - Technical Reference Manual: 258 | - Instruction set: 259 | 260 | For Development: 261 | 262 | ESP32 devkit v1 263 | 264 | ![PCA9685 Module](./doc/esp32_devkit_v1.webp) 265 | 266 | #### 1.6.5.2. Mosfets 267 | 268 | Every N-Channel Mosfet that is sure fully on @ 2.5V gate voltage 269 | 270 | I use: 271 | 272 | - IRLML6344 (37mΩ @ Ug=2.5V) 273 | - AO3400 (53mΩ @ Ug=2.5V) 274 | 275 | #### 1.6.5.3. FRAM 276 | 277 | Requirements: 278 | 279 | - I²C, min 400kHz 280 | - 3.3V VDD 281 | - Single adress byte (bigger FRAMs using two bytes for the bigger address space) 282 | 283 | I use: 284 | 285 | - MB85RC16 (16K=2K×8) () 286 | 287 | #### 1.6.5.4. PCA9685 288 | 289 | - TSSOP28 290 | - 291 | 292 | For development: PCA9685 module from ebay: 293 | ![PCA9685 Module](./doc/PCA9685-module.webp) 294 | 295 | #### 1.6.5.5. DC/DC 296 | 297 | - ESP needs up to 0.5A @3.3V (peak value). Average current consumption is 80mA. 298 | 299 | I use 24V LED so some DC/DC modules from ebay/aliexpress are fine. 300 | 301 | - Cheap and small are MP1584 based buck convertert (search for MP1584 on Ebay, Aliexpress or Amazon) 302 | - 4.5-28V to 0.8-20V and 3A (manually adjust it to 3.3V ad glue the trimmer) 303 | - 304 | 305 | ### 1.6.6. Hardware 2 (external components) 306 | 307 | #### 1.6.6.1. AC/DC 308 | 309 | I decidet to use a high efficient transformer from Enertex () which is explicit designed for PWM applications. The only disadvantage is the non waterproof design. 310 | 311 | #### 1.6.6.2. LED stripes 312 | 313 | I use high CRI 24V LED stripes from with aluminium profiles as case and heatsink. 314 | 315 | 316 | 317 | ## 1.7. Tools I used for development 318 | 319 | ### 1.7.1. Software 320 | 321 | - Visual Studio Code + PlatformIO 322 | - EasyEDA to create the PCB 323 | - JLPCB to build the PCBS 324 | - AISLER to build PCBs (more environment friendly than JLPCB because no delivery via aeroplane and default leadfree. JLPCB also provides leadfree PCBs but then the whole costs are higher than the tax-free threshold duty of 22€ in 2019). 325 | - Gimp to work with images 326 | - Sometimes Putty to connect to serial port (Serial console implementation of PlatformIO seems not to stable) 327 | 328 | ### 1.7.2. Hardware 329 | 330 | For details see the hardware section. 331 | 332 | - Esp32 DevKit v1 333 | - PCA9685 module from ebay 334 | - Breadboard 335 | - Prototype PCB for MOSFETs (I forgot to order adapter PCBs) 336 | - SOP-8 Adapter PCB from ebay for FRAM 337 | 338 | ### 1.7.3. Extended Docs 339 | 340 | - Github Markdown: 341 | - Github Markdown Code Blocks: 342 | 343 | ## 1.8. Example (My implementation) 344 | 345 | For source see here: 346 | 347 | ### 1.8.1. Prototype 348 | 349 | ![Prototype](./doc/prototype_on_breadboard_2048.webp) 350 | 351 | ### 1.8.2. Freshly produces PCB 352 | 353 | Bottom, Top 354 | 355 | ![PCB2.0](./doc/pcb_2.0_2048.webp) 356 | 357 | ### 1.8.3. Soldered PCB 358 | 359 | Some connectors are not soldered because I don't need them. 360 | 361 | No critical bugs found. Everything works as expected! 362 | 363 | #### 1.8.3.1. Known bugs 364 | 365 | - R29 must not be placed or fuse for SP. Otherwise, the flash-voltage is 1.8V instead 3.3V and the device cannot boot. 366 | - see the GPIO12(MTDI) section here: 367 | - Other option is to change the fuse 'XPD_SDIO_FORCE'. Search for 'VDD_SDIO ' here: 368 | - There a two resisitors named R2 (R2 and R2(*)) 369 | 370 | ![PCB2.0](./doc/pcb_20_soldered_2048.webp) -------------------------------------------------------------------------------- /calc-light-dimcurve/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /calc-light-dimcurve/README.md: -------------------------------------------------------------------------------- 1 | # Test Artnet Sender 2 | 3 | ## Install 4 | 5 | * Requires [NodeJs](https://nodejs.org/) 6 | * Run in this directory 7 | 8 | npm install 9 | 10 | ## Execute 11 | 12 | * Open 13 | 14 | index.js 15 | and change the host to the IP or hostname of your node 16 | * Run 17 | 18 | npm start 19 | -------------------------------------------------------------------------------- /calc-light-dimcurve/index.js: -------------------------------------------------------------------------------- 1 | // https://www.mikrocontroller.net/articles/LED-Fading 2 | 3 | const offset = 150; 4 | const pwmResoluteion = 4096; 5 | const steps = 256; 6 | 7 | const y0 = calc(0, offset); 8 | const yn = calc(steps - 1, offset); 9 | 10 | pwm(); 11 | 12 | function pwm() { 13 | console.log(`// steps: ${steps}, offset: ${offset}, pwmResolution: ${pwmResoluteion}`); 14 | console.log('const uint16_t pwmtable_16[256] PROGMEM = {'); 15 | let line = ''; 16 | const valuesPerLine = 16; 17 | count = 0; 18 | for (let i = 0; i < steps; ++i) { 19 | ++count; 20 | const f = 1 - (i / steps); 21 | const y = calc(i, offset) - (y0 * f); 22 | line += y.toFixed() 23 | // line += i; 24 | if (i !== steps - 1) { 25 | line += ', ' 26 | } 27 | if (count == valuesPerLine) { 28 | count = 0; 29 | console.log(line); 30 | line = ''; 31 | } 32 | } 33 | if (line.length > 0) { 34 | console.log(line); 35 | } 36 | console.log('};'); 37 | } 38 | 39 | function calc(i, off) { 40 | const x = i + off; 41 | const last = steps + off; 42 | return Math.pow(2, Math.log2(pwmResoluteion - 1) * (x + 1) / (last)); 43 | } -------------------------------------------------------------------------------- /calc-light-dimcurve/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calc-light-dimvalue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "nodemon": "^2.0.20" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /calc-light-dimcurve/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1: 6 | version "1.1.1" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 8 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 9 | 10 | anymatch@~3.1.2: 11 | version "3.1.3" 12 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" 13 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 14 | dependencies: 15 | normalize-path "^3.0.0" 16 | picomatch "^2.0.4" 17 | 18 | balanced-match@^1.0.0: 19 | version "1.0.2" 20 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 21 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 22 | 23 | binary-extensions@^2.0.0: 24 | version "2.2.0" 25 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 26 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 27 | 28 | brace-expansion@^1.1.7: 29 | version "1.1.11" 30 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 31 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 32 | dependencies: 33 | balanced-match "^1.0.0" 34 | concat-map "0.0.1" 35 | 36 | braces@~3.0.2: 37 | version "3.0.2" 38 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 39 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 40 | dependencies: 41 | fill-range "^7.0.1" 42 | 43 | chokidar@^3.5.2: 44 | version "3.5.3" 45 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 46 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 47 | dependencies: 48 | anymatch "~3.1.2" 49 | braces "~3.0.2" 50 | glob-parent "~5.1.2" 51 | is-binary-path "~2.1.0" 52 | is-glob "~4.0.1" 53 | normalize-path "~3.0.0" 54 | readdirp "~3.6.0" 55 | optionalDependencies: 56 | fsevents "~2.3.2" 57 | 58 | concat-map@0.0.1: 59 | version "0.0.1" 60 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 61 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 62 | 63 | debug@^3.2.7: 64 | version "3.2.7" 65 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 66 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== 67 | dependencies: 68 | ms "^2.1.1" 69 | 70 | fill-range@^7.0.1: 71 | version "7.0.1" 72 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 73 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 74 | dependencies: 75 | to-regex-range "^5.0.1" 76 | 77 | fsevents@~2.3.2: 78 | version "2.3.2" 79 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 80 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 81 | 82 | glob-parent@~5.1.2: 83 | version "5.1.2" 84 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 85 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 86 | dependencies: 87 | is-glob "^4.0.1" 88 | 89 | has-flag@^3.0.0: 90 | version "3.0.0" 91 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 92 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 93 | 94 | ignore-by-default@^1.0.1: 95 | version "1.0.1" 96 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 97 | integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== 98 | 99 | is-binary-path@~2.1.0: 100 | version "2.1.0" 101 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 102 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 103 | dependencies: 104 | binary-extensions "^2.0.0" 105 | 106 | is-extglob@^2.1.1: 107 | version "2.1.1" 108 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 109 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 110 | 111 | is-glob@^4.0.1, is-glob@~4.0.1: 112 | version "4.0.3" 113 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 114 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 115 | dependencies: 116 | is-extglob "^2.1.1" 117 | 118 | is-number@^7.0.0: 119 | version "7.0.0" 120 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 121 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 122 | 123 | minimatch@^3.1.2: 124 | version "3.1.2" 125 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 126 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 127 | dependencies: 128 | brace-expansion "^1.1.7" 129 | 130 | ms@^2.1.1: 131 | version "2.1.3" 132 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 133 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 134 | 135 | nodemon@^2.0.20: 136 | version "2.0.20" 137 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" 138 | integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw== 139 | dependencies: 140 | chokidar "^3.5.2" 141 | debug "^3.2.7" 142 | ignore-by-default "^1.0.1" 143 | minimatch "^3.1.2" 144 | pstree.remy "^1.1.8" 145 | semver "^5.7.1" 146 | simple-update-notifier "^1.0.7" 147 | supports-color "^5.5.0" 148 | touch "^3.1.0" 149 | undefsafe "^2.0.5" 150 | 151 | nopt@~1.0.10: 152 | version "1.0.10" 153 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 154 | integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== 155 | dependencies: 156 | abbrev "1" 157 | 158 | normalize-path@^3.0.0, normalize-path@~3.0.0: 159 | version "3.0.0" 160 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 161 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 162 | 163 | picomatch@^2.0.4, picomatch@^2.2.1: 164 | version "2.3.1" 165 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 166 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 167 | 168 | pstree.remy@^1.1.8: 169 | version "1.1.8" 170 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" 171 | integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== 172 | 173 | readdirp@~3.6.0: 174 | version "3.6.0" 175 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 176 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 177 | dependencies: 178 | picomatch "^2.2.1" 179 | 180 | semver@^5.7.1: 181 | version "5.7.1" 182 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 183 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 184 | 185 | semver@~7.0.0: 186 | version "7.0.0" 187 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" 188 | integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== 189 | 190 | simple-update-notifier@^1.0.7: 191 | version "1.1.0" 192 | resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" 193 | integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== 194 | dependencies: 195 | semver "~7.0.0" 196 | 197 | supports-color@^5.5.0: 198 | version "5.5.0" 199 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 200 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 201 | dependencies: 202 | has-flag "^3.0.0" 203 | 204 | to-regex-range@^5.0.1: 205 | version "5.0.1" 206 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 207 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 208 | dependencies: 209 | is-number "^7.0.0" 210 | 211 | touch@^3.1.0: 212 | version "3.1.0" 213 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 214 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== 215 | dependencies: 216 | nopt "~1.0.10" 217 | 218 | undefsafe@^2.0.5: 219 | version "2.0.5" 220 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" 221 | integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== 222 | -------------------------------------------------------------------------------- /doc/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/.gitkeep -------------------------------------------------------------------------------- /doc/PCA9685-module.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/PCA9685-module.webp -------------------------------------------------------------------------------- /doc/esp32_devkit_v1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/esp32_devkit_v1.webp -------------------------------------------------------------------------------- /doc/pcb_2.0_1024.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/pcb_2.0_1024.webp -------------------------------------------------------------------------------- /doc/pcb_2.0_2048.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/pcb_2.0_2048.webp -------------------------------------------------------------------------------- /doc/pcb_20_soldered_1024.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/pcb_20_soldered_1024.webp -------------------------------------------------------------------------------- /doc/pcb_20_soldered_2048.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/pcb_20_soldered_2048.webp -------------------------------------------------------------------------------- /doc/prototype_on_breadboard_2048.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/prototype_on_breadboard_2048.webp -------------------------------------------------------------------------------- /doc/prototype_on_breadboard_full.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/doc/prototype_on_breadboard_full.webp -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | yarn-error.log 5 | -------------------------------------------------------------------------------- /frontend/.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # ESP-Led-Dimmer Frontend 2 | 3 | ## install 4 | 5 | - install npm 6 | - install yarn global 7 | - run "yarn install" 8 | 9 | ## Build 10 | 11 | - run "yarn build" 12 | -------------------------------------------------------------------------------- /frontend/gulpfile.js: -------------------------------------------------------------------------------- 1 | const {src, dest, series, parallel, watch} = require('gulp'); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const httpProxy = require('http-proxy'); 6 | 7 | const g = { 8 | gzip: require('gulp-gzip'), 9 | htmlmin: require('gulp-htmlmin'), 10 | sass: require('gulp-sass'), 11 | cleanCSS: require('gulp-clean-css'), 12 | sourcemaps: require('gulp-sourcemaps'), 13 | terser: require('gulp-terser'), 14 | pipeline: require('readable-stream').pipeline, 15 | clean: require('gulp-clean'), 16 | concat: require('gulp-concat'), 17 | mergeStream: require('merge-stream'), 18 | manifest: require('gulp-manifest3'), 19 | plumber: require('gulp-plumber') 20 | // const htmlreplace = require('gulp-html-replace'); 21 | }; 22 | g.sass.compiler = require('node-sass'); 23 | 24 | const util = { 25 | browserSync: require('browser-sync'), 26 | readDir: require('recursive-readdir'), 27 | mimeTypes: require('mime-types') 28 | }; 29 | 30 | const server = util.browserSync.create(); 31 | 32 | // config 33 | const config = { 34 | srcDir: './src/', 35 | vendorDir: './vendor/', 36 | targetDir: './../src/webapp/', 37 | distDir: './dist/', 38 | platformIoFile: './../platformio.ini', 39 | 40 | dirInPlatformIoFile: 'src/webapp/', 41 | symbolFileName: 'files.h', 42 | webserverFileName: 'webserver.h', 43 | platformioStartTag: /^\s*;\s*\s*$/gm, 44 | platformioEndTag: /^\s*;\s*<\/Autogenerated>\s*$/gm 45 | }; 46 | 47 | function jsTask() { 48 | return g.pipeline( 49 | src(path.posix.join(config.srcDir, '*.js')), 50 | g.plumber(), 51 | g.concat('app.js'), 52 | dest(config.distDir), 53 | g.sourcemaps.init(), 54 | g.terser(), 55 | g.gzip({gzipOptions: {level: 9}}), 56 | g.sourcemaps.write('.'), 57 | dest(config.distDir)); 58 | } 59 | 60 | function cssTask() { 61 | return g.pipeline( 62 | src(path.posix.join(config.srcDir, '*.scss')), 63 | g.plumber(), 64 | g.sourcemaps.init(), 65 | g.sass().on('error', g.sass.logError), 66 | g.concat('app.css'), 67 | dest(config.distDir), 68 | g.cleanCSS({compatibility: 'ie8'}), 69 | g.gzip({gzipOptions: {level: 9}}), 70 | g.sourcemaps.write('.'), 71 | dest(config.distDir)); 72 | } 73 | 74 | function htmlTask() { 75 | return g.pipeline( 76 | src(path.posix.join(config.srcDir, '*.html')), 77 | g.plumber(), 78 | dest(config.distDir), 79 | g.htmlmin({collapseWhitespace: true}), 80 | g.gzip({gzipOptions: {level: 9}}), 81 | dest(config.distDir) 82 | ); 83 | } 84 | 85 | function vendorJsTask() { 86 | return g.pipeline( 87 | src(path.posix.join(config.vendorDir, '*.js')), 88 | g.plumber(), 89 | g.sourcemaps.init(), 90 | g.concat('vendor.js'), 91 | dest(config.distDir), 92 | g.terser(), 93 | g.gzip({gzipOptions: {level: 9}}), 94 | g.sourcemaps.write('.'), 95 | dest(config.distDir) 96 | ); 97 | } 98 | 99 | function vendorCssTask() { 100 | return g.pipeline( 101 | src(path.posix.join(config.vendorDir, '*.css')), 102 | g.plumber(), 103 | g.sourcemaps.init(), 104 | g.concat('vendor.css'), 105 | dest(config.distDir), 106 | g.cleanCSS({compatibility: 'ie8'}), 107 | g.gzip({gzipOptions: {level: 9}}), 108 | g.sourcemaps.write('.'), 109 | dest(config.distDir)); 110 | } 111 | 112 | function copyTask() { 113 | return g.pipeline( 114 | src([path.posix.join(config.distDir, '*.html.gz'), 115 | path.posix.join(config.distDir, '*.css.gz'), 116 | path.posix.join(config.distDir, '*.js.gz'), 117 | path.posix.join(config.distDir, 'appcache.manifest') 118 | ]), 119 | g.plumber(), 120 | dest(config.targetDir)); 121 | } 122 | 123 | function cleanDistTask() { 124 | return g.pipeline( 125 | src(path.posix.join(config.distDir, '**/*'), {read: false, allowEmpty: true}), 126 | g.plumber(), 127 | g.clean()); 128 | } 129 | 130 | function cleanTargetTask() { 131 | return g.pipeline( 132 | src(config.targetDir, {read: false, allowEmpty: true}), 133 | g.plumber(), 134 | g.clean({force: true})); 135 | } 136 | 137 | function replaceFilesInPlatfomrIO(newFiles) { 138 | const platformIoFileContent = fs.readFileSync(config.platformIoFile, "utf8"); 139 | const startSplit = platformIoFileContent.split(config.platformioStartTag); 140 | const prefix = startSplit[0]; 141 | const stopSplit = startSplit[1].split(config.platformioEndTag); 142 | const postfix = stopSplit[1]; 143 | 144 | let newContent = prefix + '; \n'; 145 | for (let n of newFiles) { 146 | newContent += ' ' + config.dirInPlatformIoFile + n.replace(/\\/g, '/') + '\n'; 147 | } 148 | newContent += '; \n' + postfix; 149 | 150 | fs.writeFileSync(config.platformIoFile, newContent); 151 | } 152 | 153 | function fileListTask(cb) { 154 | util.readDir(config.targetDir, ['*.txt'], (err, files) => { 155 | let fileHContent = '// !!!!! AUTO GENERATED, DO NOT EDIT !!!!!\n\n' + '#pragma once\n\n'; 156 | let webserverHContent = '// !!!!! AUTO GENERATED, DO NOT EDIT !!!!!\n\n' + '#pragma once\n\n'; 157 | let platformIoFiles = []; 158 | for (const f of files) { 159 | if (f === config.webserverFileName || f === config.symbolFileName) { 160 | continue; 161 | } 162 | const normalized = path.normalize(f); 163 | const ext = path.extname(normalized).toLowerCase(); 164 | const name = path.basename(normalized); 165 | const rawName = name.toLowerCase().endsWith('.gz') ? name.substring(0, name.length - 3) : name; 166 | const relativePath = path.relative(config.targetDir, normalized); 167 | const symbol = relativePath.replace(/\.|\/|\\|-/g, '_'); 168 | const startSymbol = symbol + '_start'; 169 | const endSymbol = symbol + '_end'; 170 | const mime = util.mimeTypes.lookup(rawName); 171 | 172 | // file.h 173 | fileHContent += '// ' + relativePath + '\n'; 174 | fileHContent += 'extern const char ' + startSymbol + '[] asm("_binary_src_webapp_' + startSymbol + '");\n'; 175 | fileHContent += 'extern const char ' + endSymbol + '[] asm("_binary_src_webapp_' + endSymbol + '");\n\n'; 176 | 177 | // webserver.h 178 | webserverHContent += 'webServer_.on("/' + (rawName === 'index.html' ? '' : rawName) + '", [this]() {\n'; 179 | webserverHContent += ' webServer_.sendHeader("Content-Encoding", "gzip");\n'; 180 | webserverHContent += ' webServer_.send_P(200, "' + mime + '", ' + startSymbol + ', ' + endSymbol + ' - ' + startSymbol + ');\n'; 181 | webserverHContent += '});\n\n'; 182 | 183 | // plaformio.ini 184 | platformIoFiles.push(relativePath.replace('/\\/g', '#')); 185 | } 186 | fs.writeFileSync(path.posix.join(config.targetDir, config.symbolFileName), fileHContent); 187 | fs.writeFileSync(path.posix.join(config.targetDir, config.webserverFileName), webserverHContent); 188 | replaceFilesInPlatfomrIO(platformIoFiles); 189 | 190 | cb(); 191 | }); 192 | } 193 | 194 | function pwaTask() { 195 | return g.pipeline( 196 | src([path.posix.join(config.distDir, '*.html'), path.posix.join(config.distDir, '*.js'), path.posix.join(config.distDir, '*.css')], {base: config.distDir}), 197 | g.manifest({hash: true, preferOnline: false, network: ['*'], filename: 'appcache.manifest'}), 198 | dest(config.distDir) 199 | ); 200 | } 201 | 202 | function reloadTask(cb) { 203 | server.reload(); 204 | cb(); 205 | } 206 | 207 | function watchTask() { 208 | let options = { 209 | server: { 210 | baseDir: config.distDir, 211 | } 212 | }; 213 | 214 | let arguments = require('commander').option('-p --proxy ', 'backend').parse(process.argv); 215 | console.log("args", arguments.opts()); 216 | 217 | if (arguments.proxy) { 218 | console.log("Start WS proxy @ ", arguments.proxy); 219 | httpProxy.createServer({ 220 | target: arguments.proxy, 221 | ws: true 222 | }).listen(81); 223 | } 224 | 225 | server.init(options); 226 | watch(path.posix.join(config.srcDir, '*.html'), series(htmlTask, pwaTask, copyTask, reloadTask)); 227 | watch(path.posix.join(config.srcDir, '*.js'), series(jsTask, pwaTask, copyTask, reloadTask)); 228 | watch(path.posix.join(config.srcDir, '*.scss'), series(cssTask, pwaTask, copyTask, reloadTask)); 229 | watch(path.posix.join(config.vendorDir, '*.js'), series(vendorJsTask, pwaTask, copyTask, reloadTask)); 230 | watch(path.posix.join(config.vendorDir, '*.css'), series(vendorCssTask, pwaTask, copyTask, reloadTask)); 231 | } 232 | 233 | // 234 | exports.cleanDist = cleanDistTask; 235 | exports.cleanTarget = cleanTargetTask; 236 | exports.clean = parallel(cleanDistTask, cleanTargetTask); 237 | exports.html = htmlTask; 238 | exports.css = cssTask; 239 | exports.vcss = vendorCssTask; 240 | exports.js = jsTask; 241 | exports.vjsc = vendorJsTask; 242 | exports.copy = copyTask; 243 | exports.files = fileListTask; 244 | exports.pwa = pwaTask; 245 | exports.watch = watchTask; 246 | exports.build = series(parallel(cleanDistTask, cleanTargetTask), parallel(jsTask, htmlTask, cssTask, vendorCssTask, vendorJsTask), pwaTask, copyTask, fileListTask); 247 | 248 | exports.default = exports.build; 249 | exports.develop = series(exports.build, watchTask); 250 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "description": "Frontend for ESP32-Led-Dimmer", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "gulp", 8 | "develop": "gulp develop" 9 | }, 10 | "author": "Lars Brandt", 11 | "license": "LGPL-3.0-or-later", 12 | "dependencies": { 13 | }, 14 | "devDependencies": { 15 | "browser-sync": "^2.26.7", 16 | "commander": "^4.0.1", 17 | "del": "^5.1.0", 18 | "gulp": "4.0.2", 19 | "gulp-clean": "^0.4.0", 20 | "gulp-clean-css": "^4.2.0", 21 | "gulp-concat": "^2.6.1", 22 | "gulp-gzip": "^1.4.2", 23 | "gulp-html-replace": "^1.6.2", 24 | "gulp-htmlmin": "^5.0.1", 25 | "gulp-livereload": "^4.0.2", 26 | "gulp-manifest3": "^0.1.2", 27 | "gulp-plumber": "^1.2.1", 28 | "gulp-sass": "^4.0.2", 29 | "gulp-sourcemaps": "^2.6.5", 30 | "gulp-terser": "^1.2.0", 31 | "http-proxy": "^1.18.0", 32 | "merge-stream": "^2.0.0", 33 | "mime-types": "^2.1.25", 34 | "node-sass": "^4.13.0", 35 | "readable-stream": "^3.4.0", 36 | "recursive-readdir": "^2.2.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/0050_utils.js: -------------------------------------------------------------------------------- 1 | const utils = {}; 2 | 3 | utils.init = () => { 4 | // nothing to do 5 | }; 6 | 7 | // thx to https://gist.github.com/jed/982883 8 | utils.hex = function () { 9 | let hex = []; 10 | 11 | for (let i = 0; i < 256; i++) { 12 | hex[i] = (i < 16 ? '0' : '') + (i).toString(16); 13 | } 14 | return hex; 15 | }(); 16 | 17 | utils.uuid = () => { 18 | let r = crypto.getRandomValues(new Uint8Array(16)); 19 | 20 | r[6] = r[6] & 0x0f | 0x40; 21 | r[8] = r[8] & 0x3f | 0x80; 22 | 23 | return ( 24 | utils.hex[r[0]] + 25 | utils.hex[r[1]] + 26 | utils.hex[r[2]] + 27 | utils.hex[r[3]] + 28 | "-" + 29 | utils.hex[r[4]] + 30 | utils.hex[r[5]] + 31 | "-" + 32 | utils.hex[r[6]] + 33 | utils.hex[r[7]] + 34 | "-" + 35 | utils.hex[r[8]] + 36 | utils.hex[r[9]] + 37 | "-" + 38 | utils.hex[r[10]] + 39 | utils.hex[r[11]] + 40 | utils.hex[r[12]] + 41 | utils.hex[r[13]] + 42 | utils.hex[r[14]] + 43 | utils.hex[r[15]] 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /frontend/src/0100_sliders.js: -------------------------------------------------------------------------------- 1 | let sliders = {}; 2 | 3 | // Options 4 | sliders.defaultSliderOptions = { 5 | value: 500.0, 6 | min: 0, 7 | max: 4095, 8 | step: 1, 9 | 10 | paddingStart: 0.05, 11 | paddingEnd: 0.05, 12 | fixedHandle: false, 13 | flipped: true, 14 | 15 | ruler: { 16 | labels: { 17 | visible: false 18 | }, 19 | tickMarks: { 20 | short: { 21 | visible: true, 22 | step: 128, 23 | pos: 0.2, 24 | size: 0.1 25 | }, 26 | long: { 27 | visible: true, 28 | step: 512, 29 | pos: 0.15, 30 | size: 0.15 31 | } 32 | } 33 | }, 34 | 35 | handle: { 36 | size: 0.1, 37 | zoom: 1.5, 38 | animation: 0 39 | } 40 | }; 41 | 42 | sliders.optionCopy = () => { 43 | return JSON.parse(JSON.stringify(sliders.defaultSliderOptions)); 44 | }; 45 | 46 | sliders.initializing = false; 47 | sliders.initAction = (cb) => { 48 | // prevent recursion of events 49 | if (sliders.initializing) { 50 | return 51 | } 52 | sliders.initializing = true; 53 | cb(); 54 | sliders.initializing = false; 55 | }; 56 | 57 | // move 58 | sliders.moving = false; 59 | sliders.ifNotMoving = (cb) => { 60 | if (!sliders.moving) { 61 | cb(); 62 | } 63 | }; 64 | 65 | sliders.moveAction = (cb) => { 66 | // prevent recursion of events 67 | if (sliders.moving) { 68 | return 69 | } 70 | sliders.moving = true; 71 | cb(); 72 | sliders.moving = false; 73 | }; 74 | 75 | // gettser/setter 76 | sliders.setSliderValue = (id, v) => { 77 | if (typeof id === 'number') { 78 | $(".single-channel input[type=range]").eq(id).rsSliderLens('option', 'value', v); 79 | } else if (id == 'single-odd') { 80 | $(".odd-channel input[type=range]").rsSliderLens('option', 'value', v); 81 | } else if (id == 'single-even') { 82 | $(".even-channel input[type=range]").rsSliderLens('option', 'value', v); 83 | } else if (id == 'single') { 84 | $(".single-channel input[type=range]").rsSliderLens('option', 'value', v); 85 | } else if (id == 'all') { 86 | $(".channel-all input[type=range]").rsSliderLens('option', 'value', v); 87 | } else if (id == 'even') { 88 | $(".channel-even input[type=range]").rsSliderLens('option', 'value', v); 89 | } else if (id == 'odd') { 90 | $(".channel-odd input[type=range]").rsSliderLens('option', 'value', v); 91 | } 92 | }; 93 | 94 | sliders.getSliderValue = (id) => { 95 | if (typeof id === 'number') { 96 | return $(".single-channel input[type=range]").eq(id).rsSliderLens('option', 'value'); 97 | } else if (id == 'all') { 98 | return $(".channel-all input[type=range]").rsSliderLens('option', 'value'); 99 | } else if (id == 'even') { 100 | return $(".channel-even input[type=range]").rsSliderLens('option', 'value'); 101 | } else if (id == 'odd') { 102 | return $(".channel-odd input[type=range]").rsSliderLens('option', 'value'); 103 | } 104 | return null; 105 | }; 106 | 107 | sliders.each = (typ, cb) => { 108 | if (typ === 'single') { 109 | $(".single-channel input[type=range]").each(cb); 110 | } else if (typ === 'all') { 111 | $(".channel-all input[type=range]").each(cb); 112 | } else if (typ === 'odd') { 113 | $(".channel-odd input[type=range]").each(cb); 114 | } else if (typ === 'even') { 115 | $(".channel-even input[type=range]").each(cb); 116 | } 117 | }; 118 | 119 | sliders.init = () => { 120 | sliders.initAction(() => { 121 | // single 122 | sliders.each('single', (i, o) => { 123 | let options = sliders.optionCopy(); 124 | options['onChange'] = () => { 125 | sliders.moveAction(() => { 126 | sliders.setSliderValue('all', 0); 127 | if (i % 2 === 0) { 128 | sliders.setSliderValue('odd', 0); 129 | } else { 130 | sliders.setSliderValue('even', 0); 131 | } 132 | sliders.change(); 133 | }); 134 | }; 135 | $(o).rsSliderLens(options); 136 | }); 137 | 138 | // all 139 | sliders.each('all', (i, o) => { 140 | let options = sliders.optionCopy(); 141 | options['onChange'] = (event, value) => { 142 | sliders.moveAction(() => { 143 | sliders.setSliderValue('single', value); 144 | sliders.setSliderValue('odd', value); 145 | sliders.setSliderValue('even', value); 146 | sliders.change(); 147 | }); 148 | }; 149 | $(o).rsSliderLens(options); 150 | }); 151 | 152 | // odd 153 | sliders.each('odd', (i, o) => { 154 | let options = sliders.optionCopy(); 155 | options['onChange'] = (event, value, isFirstHandle) => { 156 | sliders.moveAction(() => { 157 | sliders.setSliderValue('all', 0); 158 | sliders.setSliderValue('single-odd', value); 159 | sliders.change(); 160 | }); 161 | }; 162 | $(o).rsSliderLens(options); 163 | }); 164 | 165 | $(".channel-even input[type=range]").each((i, o) => { 166 | let options = sliders.optionCopy(); 167 | options['onChange'] = (event, value, isFirstHandle) => { 168 | sliders.moveAction(() => { 169 | sliders.setSliderValue('all', 0); 170 | sliders.setSliderValue('single-even', value); 171 | sliders.change(); 172 | }); 173 | }; 174 | $(o).rsSliderLens(options); 175 | }); 176 | }); 177 | }; 178 | 179 | sliders.change = () => { 180 | if (!sliders.initializing) { 181 | if (sliders.onChange) { 182 | let values = []; 183 | sliders.each('single', (i, o) => { 184 | values.push($(o).rsSliderLens('option', 'value')); 185 | }); 186 | sliders.onChange(values); 187 | } 188 | } 189 | }; 190 | 191 | sliders.setChannels = (channelArray) => { 192 | let allSame = true; 193 | let allValue = null; 194 | 195 | let oddSame = true; 196 | let oddValue = null; 197 | 198 | let evenSame = true; 199 | let evenValue = null; 200 | 201 | sliders.moveAction(() => { 202 | sliders.each('single', (i, o) => { 203 | let v = channelArray[i]; 204 | 205 | if (allValue === null) { 206 | allValue = v; 207 | } else { 208 | if (allValue !== v) { 209 | allSame = false; 210 | } 211 | } 212 | 213 | if (i % 2 === 1) { 214 | if (evenValue === null) { 215 | evenValue = v; 216 | } else { 217 | if (evenValue !== v) { 218 | evenSame = false; 219 | } 220 | } 221 | } else { 222 | if (oddValue === null) { 223 | oddValue = v; 224 | } else { 225 | if (oddValue !== v) { 226 | oddSame = false; 227 | } 228 | } 229 | } 230 | 231 | sliders.setSliderValue(i, v); 232 | }); 233 | 234 | if (allSame) { 235 | sliders.setSliderValue('all', allValue); 236 | } else { 237 | sliders.setSliderValue('all', 0); 238 | } 239 | 240 | if (oddSame) { 241 | sliders.setSliderValue('odd', oddValue); 242 | } else { 243 | sliders.setSliderValue('odd', 0); 244 | } 245 | 246 | if (evenSame) { 247 | sliders.setSliderValue('even', evenValue); 248 | } else { 249 | sliders.setSliderValue('even', 0); 250 | } 251 | }) 252 | }; 253 | 254 | sliders.onChange = null; 255 | -------------------------------------------------------------------------------- /frontend/src/0200_websocketclient.js: -------------------------------------------------------------------------------- 1 | // thx to https://github.com/websockets/ws/wiki/Websocket-client-implementation-for-auto-reconnect 2 | 3 | let wsClient = {}; 4 | 5 | wsClient.autoReconnectInterval = 5 * 1000; 6 | wsClient.url = null; 7 | wsClient.ws = null; 8 | 9 | wsClient.init = (url) => { 10 | wsClient.url = url; 11 | }; 12 | 13 | wsClient.open = () => { 14 | if (wsClient.ws) { 15 | wsClient.ws.onopen = null; 16 | wsClient.ws.onmessage = null; 17 | wsClient.ws.onclose = null; 18 | wsClient.ws.onerror = null; 19 | } 20 | 21 | wsClient.ws = new WebSocket(wsClient.url); 22 | 23 | wsClient.ws.onopen = (websocket, ev) => { 24 | // console.log('WebSocket: ON_OPEN'); 25 | wsClient.onOpen(websocket, ev) 26 | }; 27 | 28 | wsClient.ws.onmessage = (data, flags) => { 29 | // console.log('WebSocket: ON_MESSAGE', data.data); 30 | wsClient.onMessage(data.data, flags) 31 | }; 32 | 33 | wsClient.ws.onclose = (e) => { 34 | if (e.code === 1000) { 35 | // console.log('WebSocket: CLOSED', e); 36 | } else { 37 | // console.log('WebSocket: CLOSED, RECONNECT', e); 38 | wsClient.reconnect(e); 39 | } 40 | wsClient.onClose(e); 41 | }; 42 | 43 | wsClient.ws.onerror = (e) => { 44 | if (e.code === 'ECONNREFUSED') { 45 | // console.log('WebSocket: ERROR, RECONNECT', e); 46 | wsClient.reconnect(e); 47 | } else { 48 | // console.log('WebSocket: ERROR', e); 49 | wsClient.onError(e); 50 | } 51 | }; 52 | }; 53 | 54 | wsClient.send = (data) => { 55 | // console.log('WebSocket: SEND', data); 56 | wsClient.ws.send(data); 57 | }; 58 | 59 | wsClient.reconnect = (e) => { 60 | // console.log(`WebSocketClient: retry in ${this.autoReconnectInterval}ms`, e); 61 | let that = this; 62 | setTimeout(function () { 63 | // console.log("WebSocketClient: reconnecting..."); 64 | that.open(that.url); 65 | }, wsClient.autoReconnectInterval); 66 | }; 67 | 68 | wsClient.onOpen = (e) => { 69 | // console.log("WebSocketClient: open", e); 70 | }; 71 | 72 | wsClient.onMessage = (data, flags) => { 73 | console.log("WebSocketClient: message", data, flags); 74 | }; 75 | 76 | wsClient.onError = (e) => { 77 | console.log("WebSocketClient: error", e); 78 | }; 79 | 80 | wsClient.onClose = (e) => { 81 | console.log("WebSocketClient: closed", e); 82 | }; 83 | -------------------------------------------------------------------------------- /frontend/src/0900_app.js: -------------------------------------------------------------------------------- 1 | let app = {}; 2 | 3 | app.wsUrl = null; 4 | app.pendingValue = null; 5 | app.connected = false; 6 | 7 | app.idle = true; 8 | 9 | app.pollSendMessage = () => { 10 | if (app.connected && app.idle && app.pendingValue) { 11 | app.idle = false; 12 | wsClient.send(JSON.stringify(app.pendingValue)); 13 | app.pendingValue = null; 14 | } 15 | }; 16 | 17 | app.onWSMessage = (msg) => { 18 | let msgO = JSON.parse(msg); 19 | if (msgO.data) { 20 | // todo check size, values 21 | // this message is send after connecting or setting channelData otherwise (i.E. MQTT, Artnet) 22 | sliders.setChannels(app.unMapArray(msgO.data)); 23 | } else { 24 | // response of set channels has no data, only msgId 25 | app.idle = true; 26 | app.pollSendMessage(); 27 | } 28 | }; 29 | 30 | app.sendMessage = (json) => { 31 | app.pendingValue = json; 32 | app.pollSendMessage(); 33 | }; 34 | 35 | app.onSliderChange = (values) => { 36 | // console.log("APP.sliderChange", values) 37 | app.sendMessage({ 38 | msgId: utils.uuid(), 39 | data: app.mapArray(values) 40 | }); 41 | }; 42 | 43 | app.mapValue = (v) => { 44 | let a = Math.ceil(Math.pow(4095, v / 4095)); 45 | if (a > 4095) a = 4095; 46 | if (a < 0) a = 0; 47 | return a; 48 | }; 49 | 50 | app.unMapValue = (v) => { 51 | // a = pow(b, 5) 52 | // b = pow (a, 1.0 / 5); 53 | 54 | // a = Math.pow(b, c); 55 | // c = Math.log(a)/Math.log(b) 56 | 57 | // example 58 | // b = 4095 59 | // c = v/4095 60 | // a = Math.pow(4095, 100/4095) = 1.2252122450615488 61 | // c = (Math.log(a)/Math.log(b)) * 4095 62 | // c = (Math.log(1.2252122450615488)/Math.log(4095)) * 4095 = 99.99999999999996 -> ok 63 | 64 | let c = (Math.log(v) / Math.log(4095)) * 4095; 65 | if (c > 4095) c = 4095; 66 | if (c < 0) c = 0; 67 | return c; 68 | }; 69 | 70 | app.mapArray = (vA) => { 71 | let result = new Array(vA.length); 72 | for (let i = 0; i < vA.length; ++i) { 73 | result[i] = app.mapValue(vA[i]); 74 | } 75 | return result; 76 | }; 77 | 78 | app.unMapArray = (vA) => { 79 | let result = new Array(vA.length); 80 | for (let i = 0; i < vA.length; ++i) { 81 | result[i] = app.unMapValue(vA[i]); 82 | } 83 | return result; 84 | }; 85 | 86 | app.init = () => { 87 | utils.init(8); 88 | sliders.init(); 89 | 90 | wsClient.init('ws://' + new URL(window.location.href).hostname + ':81/'); 91 | wsClient.open(); 92 | 93 | wsClient.onOpen = (e) => { 94 | app.connected = true; 95 | app.pollSendMessage(); 96 | $('.connectionstate').addClass('connected'); 97 | }; 98 | 99 | wsClient.onClose = () => { 100 | app.connected = false; 101 | $('.connectionstate').removeClass('connected'); 102 | }; 103 | 104 | wsClient.onMessage = app.onWSMessage; 105 | 106 | sliders.onChange = app.onSliderChange; 107 | }; 108 | 109 | $(() => { 110 | app.init(); 111 | }); 112 | -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ESP32-Led-Dimmer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
CH1
16 |
17 | 18 |
19 | 20 |
CH2
21 |
22 | 23 |
24 | 25 |
CH3
26 |
27 | 28 |
29 | 30 |
CH4
31 |
32 | 33 |
34 | 35 |
CH5
36 |
37 | 38 |
39 | 40 |
CH6
41 |
42 | 43 |
44 | 45 |
CH7
46 |
47 | 48 |
49 | 50 |
CH8
51 |
52 | 53 |
54 | 55 |
CH9
56 |
57 | 58 |
59 | 60 |
CH10
61 |
62 |
63 | 64 |
65 |
66 | 67 |
ODD
68 |
69 |
70 | 71 |
EVEN
72 |
73 |
74 | 75 |
ALL
76 |
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /frontend/src/index.scss: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #d1d1d1; 4 | } 5 | 6 | input { 7 | width: 40px; 8 | height: 300px; 9 | } 10 | 11 | section { 12 | display: inline-block; 13 | padding: 5px; 14 | margin:2px; 15 | background-color: #202020; 16 | box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.33); 17 | } 18 | 19 | .sliderlens { 20 | border: 1px solid gray; 21 | margin: 0 10px; 22 | } 23 | 24 | .range, // range outside the handle 25 | .handle .range { // range inside the handle 26 | background: linear-gradient(to top, black 0%, yellow 80%, white 100%); 27 | } 28 | 29 | .slider-wrapper { 30 | display: inline-block; 31 | padding: 0 5px; 32 | margin: 0; 33 | 34 | .label { 35 | width:40px; 36 | text-align: center; 37 | margin: 5px 13px; 38 | color: #eeeeee; 39 | font-family: "Arial"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/vendor/200_jquery.rsSliderLens.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #454545; 3 | } 4 | .sliderlens { 5 | opacity: 0.8; 6 | cursor: pointer; 7 | } 8 | .sliderlens.focus { 9 | opacity: 1; 10 | } 11 | .sliderlens .range { 12 | background-color: #050505; 13 | border-width: 0.0625em; 14 | border-style: solid; 15 | -webkit-border-image: -webkit-linear-gradient(top, #121212, #787878) 1; 16 | -moz-border-image: -moz-linear-gradient(top, #121212, #787878) 1; 17 | -o-border-image: -o-linear-gradient(top, #121212, #787878) 1; 18 | -ms-border-image: -ms-linear-gradient(top, #121212, #787878) 1; 19 | border-image: linear-gradient(to bottom, #121212, #787878) 1; 20 | } 21 | .sliderlens .range > div { 22 | background-color: #313131; 23 | } 24 | .sliderlens .range.drag > div { 25 | cursor: ew-resize; 26 | } 27 | .sliderlens .range.drag.dragging > div { 28 | cursor: none; 29 | } 30 | .sliderlens > .handle, 31 | .sliderlens > .handle1, 32 | .sliderlens > .handle2 { 33 | border-radius: 3em/.75em; 34 | cursor: ew-resize; 35 | background-color: #454545; 36 | box-shadow: 0 0 0.3125em -0.03125em #000000; 37 | border: 0 solid #787878; 38 | border-bottom-color: #121212; 39 | border-width: 0.0625em 0; 40 | } 41 | .sliderlens > .handle:before, 42 | .sliderlens > .handle1:before, 43 | .sliderlens > .handle2:before, 44 | .sliderlens > .handle:after, 45 | .sliderlens > .handle1:after, 46 | .sliderlens > .handle2:after { 47 | content: ''; 48 | position: absolute; 49 | top: 0; 50 | right: 0; 51 | bottom: 0; 52 | left: 0; 53 | } 54 | .sliderlens > .handle.dragging, 55 | .sliderlens > .handle1.dragging, 56 | .sliderlens > .handle2.dragging { 57 | box-shadow: 0 0 0.3125em -0.0625em #000000; 58 | } 59 | .sliderlens > .handle .range, 60 | .sliderlens > .handle1 .range, 61 | .sliderlens > .handle2 .range { 62 | -webkit-border-image: -webkit-linear-gradient(top, #121212, #d1d1d1) 1; 63 | -moz-border-image: -moz-linear-gradient(top, #121212, #d1d1d1) 1; 64 | -o-border-image: -o-linear-gradient(top, #121212, #d1d1d1) 1; 65 | -ms-border-image: -ms-linear-gradient(top, #121212, #d1d1d1) 1; 66 | border-image: linear-gradient(to bottom, #121212, #d1d1d1) 1; 67 | } 68 | .sliderlens > .handle:before { 69 | right: 50%; 70 | bottom: 55%; 71 | border-right: 0.0625em solid rgba(186, 186, 186, 0.6); 72 | z-index: 1; 73 | } 74 | .sliderlens > .handle:after { 75 | background: -webkit-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 76 | background: -moz-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 77 | background: -o-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 78 | background: -ms-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 79 | background: linear-gradient(to right, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 80 | } 81 | .sliderlens > .handle1 { 82 | border-top-right-radius: 0; 83 | border-bottom-right-radius: 0; 84 | } 85 | .sliderlens > .handle1:after { 86 | background: -webkit-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 87 | background: -moz-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 88 | background: -o-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 89 | background: -ms-linear-gradient(left, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 90 | background: linear-gradient(to right, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 91 | } 92 | .sliderlens > .handle2 { 93 | border-top-left-radius: 0; 94 | border-bottom-left-radius: 0; 95 | } 96 | .sliderlens > .handle2:after { 97 | background: -webkit-linear-gradient(left, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 98 | background: -moz-linear-gradient(left, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 99 | background: -o-linear-gradient(left, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 100 | background: -ms-linear-gradient(left, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 101 | background: linear-gradient(to right, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 102 | } 103 | .sliderlens svg > path { 104 | stroke: rgba(186, 186, 186, 0.6); 105 | } 106 | .sliderlens svg > g > text { 107 | fill: rgba(186, 186, 186, 0.6); 108 | font-size: 0.5em; 109 | } 110 | .sliderlens.vert > .handle, 111 | .sliderlens.vert > .handle1, 112 | .sliderlens.vert > .handle2 { 113 | cursor: ns-resize; 114 | border-radius: 3em/.75em; 115 | } 116 | .sliderlens.vert > .handle .range, 117 | .sliderlens.vert > .handle1 .range, 118 | .sliderlens.vert > .handle2 .range { 119 | -webkit-border-image: -webkit-linear-gradient(top, #121212, #787878) 1; 120 | -moz-border-image: -moz-linear-gradient(top, #121212, #787878) 1; 121 | -o-border-image: -o-linear-gradient(top, #121212, #787878) 1; 122 | -ms-border-image: -ms-linear-gradient(top, #121212, #787878) 1; 123 | border-image: linear-gradient(to bottom, #121212, #787878) 1; 124 | } 125 | .sliderlens.vert > .handle:before { 126 | right: 55%; 127 | bottom: 50%; 128 | border-right: none; 129 | border-bottom: 0.0625em solid rgba(186, 186, 186, 0.6); 130 | } 131 | .sliderlens.vert > .handle:after { 132 | background: -webkit-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 133 | background: -moz-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 134 | background: -o-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 135 | background: -ms-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 136 | background: linear-gradient(to bottom, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 50%, rgba(56, 56, 56, 0.25) 50%, rgba(56, 56, 56, 0.95) 100%); 137 | } 138 | .sliderlens.vert > .handle1 { 139 | border-bottom: none; 140 | border-bottom-left-radius: 0; 141 | border-bottom-right-radius: 0; 142 | } 143 | .sliderlens.vert > .handle1:after { 144 | background: -webkit-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 145 | background: -moz-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 146 | background: -o-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 147 | background: -ms-linear-gradient(top, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 148 | background: linear-gradient(to bottom, rgba(69, 69, 69, 0.95) 0%, rgba(120, 120, 120, 0.25) 100%); 149 | } 150 | .sliderlens.vert > .handle2 { 151 | border-top: none; 152 | border-top-left-radius: 0; 153 | border-top-right-radius: 0; 154 | } 155 | .sliderlens.vert > .handle2:after { 156 | background: -webkit-linear-gradient(top, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 157 | background: -moz-linear-gradient(top, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 158 | background: -o-linear-gradient(top, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 159 | background: -ms-linear-gradient(top, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 160 | background: linear-gradient(to bottom, rgba(56, 56, 56, 0.25) 0%, rgba(56, 56, 56, 0.95) 100%); 161 | } 162 | .sliderlens.fixed { 163 | border-width: 0.0625em; 164 | border-style: solid; 165 | -webkit-border-image: -webkit-linear-gradient(top, #121212, #787878) 1; 166 | -moz-border-image: -moz-linear-gradient(top, #121212, #787878) 1; 167 | -o-border-image: -o-linear-gradient(top, #121212, #787878) 1; 168 | -ms-border-image: -ms-linear-gradient(top, #121212, #787878) 1; 169 | border-image: linear-gradient(to bottom, #121212, #787878) 1; 170 | background-color: #383838; 171 | cursor: ew-resize; 172 | } 173 | .sliderlens.fixed:before, 174 | .sliderlens.fixed:after { 175 | content: ''; 176 | position: absolute; 177 | top: 0; 178 | right: 0; 179 | bottom: 0; 180 | left: 0; 181 | border-width: 0 0 0.0625em 0; 182 | border-style: solid; 183 | pointer-events: none; 184 | -webkit-border-image: -webkit-linear-gradient(top, #050505, #5f5f5f) 1; 185 | -moz-border-image: -moz-linear-gradient(top, #050505, #5f5f5f) 1; 186 | -o-border-image: -o-linear-gradient(top, #050505, #5f5f5f) 1; 187 | -ms-border-image: -ms-linear-gradient(top, #050505, #5f5f5f) 1; 188 | border-image: linear-gradient(to bottom, #050505, #5f5f5f) 1; 189 | } 190 | .sliderlens.fixed:after { 191 | border-width: 0 0.0625em; 192 | box-shadow: inset 1em 0 1.25em -0.5em #000000, inset -1em 0 1.25em -0.5em #000000; 193 | } 194 | .sliderlens.fixed.vert { 195 | cursor: ns-resize; 196 | } 197 | .sliderlens.fixed.vert:before { 198 | border-width: 0 0.0625em; 199 | } 200 | .sliderlens.fixed.vert:after { 201 | border-width: 0 0 0.0625em 0; 202 | box-shadow: inset 0 1em 1.25em -0.5em #000000, inset 0 -1em 1.25em -0.5em #000000; 203 | } 204 | .sliderlens.fixed > .handle { 205 | border-radius: 0; 206 | border: none; 207 | } 208 | .sliderlens.vert .range.drag > div { 209 | cursor: ns-resize; 210 | } 211 | .sliderlens.dragging { 212 | cursor: none; 213 | } 214 | .sliderlens.dragging > .handle, 215 | .sliderlens.dragging > .handle1, 216 | .sliderlens.dragging > .handle2 { 217 | cursor: none; 218 | } 219 | a { 220 | display: block; 221 | margin-top: 2em; 222 | color: white; 223 | } 224 | section { 225 | margin: 1em 3em; 226 | } 227 | p { 228 | margin-top: 3em; 229 | color: #eee; 230 | } 231 | -------------------------------------------------------------------------------- /partitions_custom.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x1E0000, 5 | app1, app, ota_1, 0x1F0000,0x1E0000, 6 | spiffs, data, spiffs, 0x3D0000,0x030000, 7 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ;PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | ; DEVELOP -> Serial port 13 | default_envs = develop 14 | 15 | ; PROD -> flash device OTA 16 | ;default_envs = prod-flash 17 | 18 | [env:prod-flash] 19 | ; change Host to your esp32 node 20 | upload_protocol = espota 21 | upload_port = 192.168.178.34 22 | 23 | ; change serial port to your port 24 | #monitor_port = COM7 25 | monitor_speed = 115200 26 | 27 | 28 | 29 | [env:develop] 30 | build_flags = 31 | ; -DAC_DEBUG=true 32 | ; -DCORE_DEBUG_LEVEL=4 33 | 34 | ; change Host to your esp32 node 35 | upload_protocol = espota 36 | upload_port = 192.168.178.23 37 | ; upload_protocol = esptool 38 | ; upload_speed = 921600 39 | 40 | monitor_speed = 115200 41 | 42 | 43 | ; JTAG debug 44 | ; change "adapter_khz 20000" to "adapter_khz 1000" in 45 | ; .platformio\packages\tool-openocd-esp32\share\openocd\scripts\board\esp-wroom-32.cfg 46 | debug_tool = olimex-arm-usb-ocd-h 47 | 48 | [env] 49 | framework = arduino 50 | platform = espressif32 51 | ; esp32-evb uses esp-wroom-32.cfg 52 | board = esp32-evb 53 | board_build.partitions = partitions_custom.csv 54 | 55 | build_flags = 56 | -DMQTT_MAX_PACKET_SIZE=1024 57 | -DAUTOCONNECT_TICKER_PORT=2 58 | -D CURRENT_TIME=$UNIX_TIME 59 | -D DEBUG_ESP_PORT=true 60 | -S DEBUG_WEBSOCKETS=true 61 | 62 | board_build.embed_txtfiles = 63 | src/config/configserver_menu.json 64 | 65 | ; these files are NOT automaticly inserted by frontend task 66 | board_build.embed_files = 67 | ; put other files here 68 | ; ... 69 | ; Don't touch the following block. It will be replaced by the frontend gulp task. 70 | ; 71 | src/webapp/appcache.manifest 72 | src/webapp/app.js.gz 73 | src/webapp/app.css.gz 74 | src/webapp/index.html.gz 75 | src/webapp/vendor.css.gz 76 | src/webapp/vendor.js.gz 77 | ; 78 | 79 | lib_deps = 80 | forkineye/ESPAsyncE131@^1.0.4 81 | https://github.com/Hieromon/AutoConnect.git @1.4.1 82 | https://github.com/bblanchon/ArduinoJson.git @6.20.0 83 | https://github.com/FaBoPlatform/FaBoPWM-PCA9685-Library.git @1.0.0 84 | https://github.com/bakercp/CRC32.git @2.0.0 85 | https://github.com/Links2004/arduinoWebSockets.git @2.3.7 86 | knolleary/PubSubClient @^2.8 87 | -------------------------------------------------------------------------------- /src/config/config.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "config.h" 19 | 20 | #include 21 | 22 | //------------------------------------------------------------------------------ 23 | bool Config::fromJson(JsonDocument& doc) 24 | //------------------------------------------------------------------------------ 25 | { 26 | bool complete = true; 27 | 28 | complete &= doc.containsKey(JSON_DEVICENAME); 29 | deviceName_ = doc[JSON_DEVICENAME].as(); 30 | 31 | complete &= doc.containsKey(JSON_TOPIC); 32 | topic_ = doc[JSON_TOPIC].as(); 33 | 34 | complete &= doc.containsKey(JSON_MQTTSERVER); 35 | mqttServer_ = doc[JSON_MQTTSERVER].as(); 36 | 37 | complete &= doc.containsKey(JSON_USERNAME); 38 | username_ = doc[JSON_USERNAME].as(); 39 | 40 | complete &= doc.containsKey(JSON_PASSWORD); 41 | password_ = doc[JSON_PASSWORD].as(); 42 | 43 | complete &= doc.containsKey(JSON_ARTNET_UNIVERSE); 44 | artnetUniverse_ = doc[JSON_ARTNET_UNIVERSE].as(); 45 | 46 | return complete; 47 | } 48 | 49 | //------------------------------------------------------------------------------ 50 | void Config::toJson(JsonDocument& doc) 51 | //------------------------------------------------------------------------------ 52 | { 53 | doc[JSON_DEVICENAME] = deviceName_; 54 | doc[JSON_TOPIC] = topic_; 55 | doc[JSON_MQTTSERVER] = mqttServer_; 56 | doc[JSON_USERNAME] = username_; 57 | doc[JSON_PASSWORD] = password_; 58 | doc[JSON_ARTNET_UNIVERSE] = artnetUniverse_; 59 | } 60 | 61 | //------------------------------------------------------------------------------ 62 | void Config::dump() 63 | //------------------------------------------------------------------------------ 64 | { 65 | Serial.println("----- Config dump -----"); 66 | DynamicJsonDocument doc(1024); 67 | toJson(doc); 68 | serializeJsonPretty(doc, Serial); 69 | Serial.println("----- /Config dump -----"); 70 | } 71 | 72 | //------------------------------------------------------------------------------ 73 | bool Config::load() 74 | //------------------------------------------------------------------------------ 75 | { 76 | if (!SPIFFS.begin(true)) { 77 | Serial.println("CONFIG.ERROR: could not mount SPIFFSn"); 78 | return false; 79 | } 80 | 81 | bool result = false; 82 | DynamicJsonDocument doc(1024); 83 | if (!SPIFFS.exists(CONFIG_FILE)) { 84 | Serial.println("CONFIG.WARN: config file does not exist."); 85 | return false; 86 | } 87 | 88 | File configFile = SPIFFS.open(CONFIG_FILE, "r"); 89 | if (configFile) { 90 | DeserializationError error = deserializeJson(doc, configFile); 91 | if (error) { 92 | Serial.printf("CONFIG.ERROR: could not parse config json: '%s'.", error.c_str()); 93 | } else { 94 | result = fromJson(doc); 95 | if (!result) { 96 | Serial.println("CONFIG.ERROR: data is incomplete"); 97 | } 98 | } 99 | configFile.close(); 100 | } else { 101 | Serial.println("CONFIG.INFO: config file not foundn"); 102 | } 103 | 104 | SPIFFS.end(); 105 | return result; 106 | } 107 | 108 | //------------------------------------------------------------------------------ 109 | void Config::save() 110 | //------------------------------------------------------------------------------ 111 | { 112 | if (!SPIFFS.begin(true)) { 113 | Serial.println("CONFIG.ERROR: could not mount SPIFFS."); 114 | return; 115 | } 116 | 117 | DynamicJsonDocument doc(1024); 118 | toJson(doc); 119 | 120 | File configFile = SPIFFS.open(CONFIG_FILE, "w"); 121 | serializeJson(doc, Serial); 122 | if (configFile) { 123 | serializeJson(doc, configFile); 124 | configFile.close(); 125 | } else { 126 | Serial.println("CONFIG.ERROR: couldn not open config for to writing"); 127 | } 128 | 129 | SPIFFS.end(); 130 | } 131 | -------------------------------------------------------------------------------- /src/config/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include 23 | 24 | #define CONFIG_FILE "/config.json" 25 | 26 | #define JSON_DEVICENAME "deviceName" 27 | #define JSON_TOPIC "topic" 28 | #define JSON_MQTTSERVER "mqttServer" 29 | #define JSON_USERNAME "userName" 30 | #define JSON_PASSWORD "password" 31 | #define JSON_ARTNET_UNIVERSE "artnetUniverse" 32 | 33 | /** 34 | * Load and Save Configuration Data to/from flash. 35 | * 36 | * This Data can only rarely written because the flash cells have a short amount of write cycle before they die. 37 | * 38 | * In case of ESP32-WROOM-32 module, the Datasheet (https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf) 39 | * doesn't say which chip is soldered. 40 | * But i found pictures of the opened device in the internet and saw that one version uses 41 | * a FM25Q32 (http://www.fmsh.com/AjaxFile/DownLoadFile.aspx?FilePath=/UpLoadFile/20150401/FM25Q32_ds_eng.pdf&fileExt=file) 42 | * wich has 100.000 program/erase cycles. What is much more than i expected. 43 | * Another version has a GD25Q32 (http://www.elm-tech.com/en/products/spi-flash-memory/gd25q32/gd25q32.pdf) 44 | * which also has 100.000 program/erase cycles. 45 | * Here (https://forum.makehackvoid.com/t/playing-with-the-esp-32/1144/21) is an excellent image with another version. 46 | * 47 | * In short: All Datasheet i saw have 100.000 program/erase cycles. 48 | */ 49 | class Config { 50 | public: 51 | const String& getDeviceName() const { return deviceName_; }; 52 | void setDeviceName(String value) { deviceName_ = value; }; 53 | 54 | const String& getTopic() const { return topic_; }; 55 | void setTopic(String value) { topic_ = value; }; 56 | 57 | const String& getMqttServer() const { return mqttServer_; }; 58 | void setMqttServer(String value) { mqttServer_ = value; }; 59 | 60 | const String& getUsername() const { return username_; }; 61 | void setUsername(String value) { username_ = value; }; 62 | 63 | const String& getPassword() const { return password_; }; 64 | void setPassword(String value) { password_ = value; }; 65 | 66 | const uint16_t getArtnetUniverse() const { return artnetUniverse_; }; 67 | void setArtnetUniverse(uint16_t value) { artnetUniverse_ = value; }; 68 | 69 | bool fromJson(JsonDocument& doc); 70 | void toJson(JsonDocument& doc); 71 | void dump(); 72 | bool load(); 73 | void save(); 74 | 75 | protected: 76 | // device stuff 77 | String deviceName_; 78 | 79 | // mqtt stuff 80 | String topic_; 81 | String mqttServer_; 82 | String username_; 83 | String password_; 84 | uint16_t artnetUniverse_; 85 | }; 86 | -------------------------------------------------------------------------------- /src/config/configserver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "configserver.h" 19 | 20 | #include 21 | 22 | #include "webapp/files.h" 23 | extern const char configServerMenu[] asm("_binary_src_config_configserver_menu_json_start"); 24 | 25 | //------------------------------------------------------------------------------ 26 | ConfigServer::ConfigServer() 27 | : LOG("ConfigServer"), 28 | webServer_(), 29 | autoConnect_(webServer_), 30 | onDeviceSetCB_(NULL), 31 | onMqttSetCB_(NULL) 32 | //------------------------------------------------------------------------------ 33 | {} 34 | 35 | //------------------------------------------------------------------------------ 36 | bool ConfigServer::begin(String& title) 37 | //------------------------------------------------------------------------------ 38 | { 39 | #include "webapp/webserver.h" 40 | 41 | webServer_.on("/device_set", std::bind(&ConfigServer::onDeviceSet_, this)); 42 | webServer_.on("/mqtt_set", std::bind(&ConfigServer::onMqttSet_, this)); 43 | webServer_.on("/artnet_set", std::bind(&ConfigServer::onArtnetSet_, this)); 44 | 45 | AutoConnectConfig autoConnectConfig; 46 | autoConnectConfig.title = title; 47 | autoConnectConfig.hostName = title; 48 | autoConnectConfig.autoReconnect = true; 49 | autoConnect_.config(autoConnectConfig); 50 | 51 | // load config 52 | bool result = autoConnect_.load(configServerMenu); 53 | if (result) { 54 | LOG.i("AutoConnect loaded."); 55 | } else { 56 | LOG.i("ERROR: Autoconnect load failed."); 57 | } 58 | 59 | // start 60 | if (result) { 61 | result = autoConnect_.begin(); 62 | if (result) { 63 | LOG.i("AutoConnect started."); 64 | } else { 65 | LOG.i("ERROR: Autoconnect start failed."); 66 | } 67 | } 68 | 69 | return result; 70 | } 71 | 72 | //------------------------------------------------------------------------------ 73 | void ConfigServer::loop() 74 | //------------------------------------------------------------------------------ 75 | { 76 | autoConnect_.handleClient(); 77 | } 78 | 79 | //------------------------------------------------------------------------------ 80 | void ConfigServer::setDeviceData(DeviceData& data) 81 | //------------------------------------------------------------------------------ 82 | { 83 | AutoConnectAux* device = autoConnect_.aux("/device"); 84 | if (!device) { 85 | Serial.println("[ConfigServer] Error: could not found device section in configuration"); 86 | return; 87 | } 88 | AutoConnectInput& deviceName = device->getElement("devicename"); 89 | deviceName.value = data.deviceName; 90 | } 91 | 92 | //------------------------------------------------------------------------------ 93 | void ConfigServer::setMqttData(MqttData& data) 94 | //------------------------------------------------------------------------------ 95 | { 96 | AutoConnectAux* mqtt = autoConnect_.aux("/mqtt"); 97 | if (!mqtt) { 98 | Serial.println("[ConfigServer] Error: could not found mqtt section in configuration"); 99 | return; 100 | } 101 | AutoConnectInput& topic = mqtt->getElement("topic"); 102 | topic.value = data.topic; 103 | 104 | AutoConnectInput& mqttServer = mqtt->getElement("mqtt-server"); 105 | mqttServer.value = data.server; 106 | 107 | AutoConnectInput& username = mqtt->getElement("username"); 108 | username.value = data.userName; 109 | 110 | AutoConnectInput& password = mqtt->getElement("password"); 111 | password.value = data.password; 112 | } 113 | 114 | //------------------------------------------------------------------------------ 115 | void ConfigServer::setArtnetData(ArtnetData& data) 116 | //------------------------------------------------------------------------------ 117 | { 118 | AutoConnectAux* mqtt = autoConnect_.aux("/artnet"); 119 | if (!mqtt) { 120 | Serial.println("[ConfigServer] Error: could not found artnet section in configuration"); 121 | return; 122 | } 123 | AutoConnectInput& topic = mqtt->getElement("universe"); 124 | topic.value = data.universe; 125 | } 126 | 127 | //------------------------------------------------------------------------------ 128 | void ConfigServer::onDeviceSet(deviceSetFunction_t f) 129 | //------------------------------------------------------------------------------ 130 | { 131 | onDeviceSetCB_ = f; 132 | } 133 | 134 | //------------------------------------------------------------------------------ 135 | void ConfigServer::onMqttSet(mqttSetFunction_t f) 136 | //------------------------------------------------------------------------------ 137 | { 138 | onMqttSetCB_ = f; 139 | } 140 | 141 | //------------------------------------------------------------------------------ 142 | void ConfigServer::onArtnetSet(artnetSetFunction_t f) 143 | //------------------------------------------------------------------------------ 144 | { 145 | onArtnetSetCB_ = f; 146 | } 147 | 148 | //------------------------------------------------------------------------------ 149 | void ConfigServer::onDeviceSet_() 150 | //------------------------------------------------------------------------------ 151 | { 152 | if (onDeviceSetCB_ != NULL) { 153 | DeviceData data; 154 | data.deviceName = webServer_.arg("devicename"); 155 | onDeviceSetCB_(data); 156 | } 157 | redirect("/device"); 158 | } 159 | 160 | //------------------------------------------------------------------------------ 161 | void ConfigServer::onMqttSet_() 162 | //------------------------------------------------------------------------------ 163 | { 164 | if (onMqttSetCB_ != NULL) { 165 | MqttData data; 166 | data.topic = webServer_.arg("topic"); 167 | data.server = webServer_.arg("mqtt-server"); 168 | data.userName = webServer_.arg("username"); 169 | data.password = webServer_.arg("password"); 170 | onMqttSetCB_(data); 171 | } 172 | redirect("/mqtt"); 173 | } 174 | //------------------------------------------------------------------------------ 175 | void ConfigServer::onArtnetSet_() 176 | //------------------------------------------------------------------------------ 177 | { 178 | if (onArtnetSetCB_ != NULL) { 179 | ArtnetData data; 180 | data.universe = webServer_.arg("universe").toInt(); 181 | onArtnetSetCB_(data); 182 | } 183 | redirect("/artnet"); 184 | } 185 | 186 | //------------------------------------------------------------------------------ 187 | void ConfigServer::redirect(const String toLocation) 188 | //------------------------------------------------------------------------------ 189 | { 190 | webServer_.sendHeader("Location", toLocation, true); 191 | webServer_.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. 192 | webServer_.client().stop(); 193 | } 194 | -------------------------------------------------------------------------------- /src/config/configserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include "util/logger.h" 25 | 26 | class DeviceData { 27 | public: 28 | String deviceName; 29 | }; 30 | 31 | class MqttData { 32 | public: 33 | String topic; 34 | String server; 35 | String userName; 36 | String password; 37 | }; 38 | 39 | class ArtnetData { 40 | public: 41 | uint16_t universe; 42 | }; 43 | 44 | class ConfigServer { 45 | public: 46 | typedef std::function deviceSetFunction_t; 47 | typedef std::function mqttSetFunction_t; 48 | typedef std::function artnetSetFunction_t; 49 | 50 | ConfigServer(); 51 | 52 | bool begin(String& title); 53 | void loop(); 54 | void setDeviceData(DeviceData& data); 55 | void setMqttData(MqttData& data); 56 | void setArtnetData(ArtnetData& data); 57 | void onDeviceSet(deviceSetFunction_t f); 58 | void onMqttSet(mqttSetFunction_t f); 59 | void onArtnetSet(artnetSetFunction_t f); 60 | 61 | private: 62 | void onDeviceSet_(); 63 | void onMqttSet_(); 64 | void onArtnetSet_(); 65 | void redirect(const String toLocation); 66 | 67 | Logger LOG; 68 | WebServer webServer_; 69 | AutoConnect autoConnect_; 70 | deviceSetFunction_t onDeviceSetCB_; 71 | mqttSetFunction_t onMqttSetCB_; 72 | artnetSetFunction_t onArtnetSetCB_; 73 | }; 74 | -------------------------------------------------------------------------------- /src/config/configserver_menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Device", 4 | "uri": "/device", 5 | "menu": true, 6 | "element": [ 7 | { 8 | "name": "devicename", 9 | "type": "ACInput", 10 | "label": "Devicename", 11 | "placeholder": "Devicename" 12 | }, 13 | { 14 | "name": "set", 15 | "type": "ACSubmit", 16 | "value": "SET", 17 | "uri": "/device_set" 18 | } 19 | ] 20 | }, 21 | { 22 | "title": "Artnet", 23 | "uri": "/artnet", 24 | "menu": true, 25 | "element": [ 26 | { 27 | "name": "universe", 28 | "type": "ACInput", 29 | "label": "Universe", 30 | "placeholder": "Universe" 31 | }, 32 | { 33 | "name": "set", 34 | "type": "ACSubmit", 35 | "value": "SET", 36 | "uri": "/artnet_set" 37 | } 38 | ] 39 | }, 40 | { 41 | "title": "Mqtt", 42 | "uri": "/mqtt", 43 | "menu": true, 44 | "element": [ 45 | { 46 | "name": "mqtt-server", 47 | "type": "ACInput", 48 | "label": "MQTT-Server", 49 | "placeholder": "MQTT-Server" 50 | }, 51 | { 52 | "name": "username", 53 | "type": "ACInput", 54 | "label": "User", 55 | "placeholder": "Username" 56 | }, 57 | { 58 | "name": "password", 59 | "type": "ACInput", 60 | "label": "Password", 61 | "placeholder": "Password" 62 | }, 63 | { 64 | "name": "topic", 65 | "type": "ACInput", 66 | "label": "Topic", 67 | "placeholder": "Topic" 68 | }, 69 | { 70 | "name": "set", 71 | "type": "ACSubmit", 72 | "value": "SET", 73 | "uri": "/mqtt_set" 74 | } 75 | ] 76 | } 77 | ] -------------------------------------------------------------------------------- /src/hardware/fm24c04.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "fm24c04.h" 19 | 20 | #include 21 | #include 22 | 23 | FM24C04::FM24C04(void) { _framInitialised = false; } 24 | 25 | //------------------------------------------------------------------------------ 26 | bool FM24C04::begin(uint8_t addr) 27 | //------------------------------------------------------------------------------ 28 | { 29 | i2c_addr = addr; 30 | 31 | Wire.beginTransmission(i2c_addr); 32 | Wire.write(0); 33 | if (Wire.endTransmission() == 0) { 34 | _framInitialised = true; 35 | return true; 36 | } else { 37 | return false; 38 | } 39 | } 40 | 41 | //------------------------------------------------------------------------------ 42 | void FM24C04::write8(uint8_t framAddr, uint8_t value) 43 | //------------------------------------------------------------------------------ 44 | { 45 | Wire.beginTransmission(i2c_addr); 46 | Wire.write(framAddr); 47 | Wire.write(value); 48 | Wire.endTransmission(); 49 | } 50 | 51 | //------------------------------------------------------------------------------ 52 | uint8_t FM24C04::read8(uint8_t framAddr) const 53 | //------------------------------------------------------------------------------ 54 | { 55 | Wire.beginTransmission(i2c_addr); 56 | Wire.write(framAddr); 57 | Wire.endTransmission(); 58 | 59 | Wire.requestFrom(i2c_addr, (uint8_t)1); 60 | 61 | return Wire.read(); 62 | } 63 | 64 | //------------------------------------------------------------------------------ 65 | void FM24C04::write16(uint8_t framAddr, uint16_t value) 66 | //------------------------------------------------------------------------------ 67 | { 68 | Wire.beginTransmission(i2c_addr); 69 | Wire.write(framAddr); 70 | Wire.write((value >> 8) & 0xff); 71 | Wire.write(value & 0xff); 72 | Wire.endTransmission(); 73 | } 74 | 75 | //------------------------------------------------------------------------------ 76 | uint16_t FM24C04::read16(uint8_t framAddr) const 77 | //------------------------------------------------------------------------------ 78 | { 79 | Wire.beginTransmission(i2c_addr); 80 | Wire.write(framAddr); 81 | Wire.endTransmission(); 82 | 83 | Wire.requestFrom(i2c_addr, (uint8_t)2); 84 | 85 | uint16_t result = Wire.read(); 86 | return (result << 8) + Wire.read(); 87 | } 88 | 89 | //------------------------------------------------------------------------------ 90 | void FM24C04::write32(uint8_t framAddr, uint32_t value) 91 | //------------------------------------------------------------------------------ 92 | { 93 | Wire.beginTransmission(i2c_addr); 94 | Wire.write(framAddr); 95 | Wire.write((value >> 24) & 0xff); 96 | Wire.write((value >> 16) & 0xff); 97 | Wire.write((value >> 8) & 0xff); 98 | Wire.write(value & 0xff); 99 | Wire.endTransmission(); 100 | } 101 | 102 | //------------------------------------------------------------------------------ 103 | uint32_t FM24C04::read32(uint8_t framAddr) const 104 | //------------------------------------------------------------------------------ 105 | { 106 | Wire.beginTransmission(i2c_addr); 107 | Wire.write(framAddr); 108 | Wire.endTransmission(); 109 | 110 | Wire.requestFrom(i2c_addr, (uint8_t)4); 111 | 112 | uint32_t result = Wire.read(); 113 | result = (result << 8) | Wire.read(); 114 | result = (result << 8) | Wire.read(); 115 | return (result << 8) + Wire.read(); 116 | } 117 | 118 | //------------------------------------------------------------------------------ 119 | void FM24C04::print_wire_result(uint8_t r) const 120 | //------------------------------------------------------------------------------ 121 | { 122 | if (r == 0) { 123 | Serial.println("FRAM: success"); 124 | } else if (r == 1) { 125 | Serial.println("FRAM: data too long to fit in transmit buffer"); 126 | } else if (r == 2) { 127 | Serial.println("FRAM: received NACK on transmit of address"); 128 | } else if (r == 3) { 129 | Serial.println("FRAM: received NACK on transmit of data"); 130 | } else if (r == 4) { 131 | Serial.println("FRAM: other error"); 132 | } else { 133 | Serial.println("FRAM: unknown error"); 134 | } 135 | } 136 | 137 | //------------------------------------------------------------------------------ 138 | void FM24C04::setRange(uint8_t from, uint8_t to, uint8_t with) 139 | //------------------------------------------------------------------------------ 140 | { 141 | for (uint8_t i = from; i <= to; ++i) { 142 | write8(i, with); 143 | } 144 | } 145 | 146 | //------------------------------------------------------------------------------ 147 | void FM24C04::refreshChecksum(uint8_t from, uint8_t to, uint8_t checksumAdr) 148 | //------------------------------------------------------------------------------ 149 | { 150 | uint32_t checksum = calcChecksum(from, to); 151 | write32(checksumAdr, checksum); 152 | } 153 | 154 | //------------------------------------------------------------------------------ 155 | bool FM24C04::validate(uint8_t from, uint8_t to, uint8_t checksumAdr) const 156 | //------------------------------------------------------------------------------ 157 | { 158 | uint32_t checksum = calcChecksum(from, to); 159 | uint32_t storedChecksum = read32(checksumAdr); 160 | return (checksum == storedChecksum); 161 | } 162 | 163 | //------------------------------------------------------------------------------ 164 | uint32_t FM24C04::calcChecksum(uint8_t from, uint8_t to) const 165 | //------------------------------------------------------------------------------ 166 | { 167 | CRC32 crc; 168 | for (uint8_t i = from; i <= to; ++i) { 169 | crc.update(read8(i)); 170 | } 171 | return crc.finalize(); 172 | } 173 | -------------------------------------------------------------------------------- /src/hardware/fm24c04.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #define FM24C04_DEFAULT_ADDRESS \ 23 | (0x50) /* 1010 + A2 + A1 + A0 = 0x50 default \ \ \ \ \ 24 | */ 25 | 26 | class FM24C04 { 27 | public: 28 | FM24C04(void); 29 | 30 | bool begin(uint8_t addr = FM24C04_DEFAULT_ADDRESS); 31 | void write8(uint8_t framAddr, uint8_t value); 32 | uint8_t read8(uint8_t framAddr) const; 33 | void write16(uint8_t framAddr, uint16_t value); 34 | uint16_t read16(uint8_t framAddr) const; 35 | void write32(uint8_t framAddr, uint32_t value); 36 | uint32_t read32(uint8_t framAddr) const; 37 | 38 | void setRange(uint8_t from, uint8_t to, uint8_t with); 39 | void refreshChecksum(uint8_t from, uint8_t to, uint8_t checksumAddr); 40 | /** 41 | * @return true if valid 42 | */ 43 | bool validate(uint8_t from, uint8_t to, uint8_t checksumAddr) const; 44 | uint32_t calcChecksum(uint8_t from, uint8_t to) const; 45 | 46 | protected: 47 | void print_wire_result(uint8_t r) const; 48 | 49 | private: 50 | uint8_t i2c_addr; 51 | bool _framInitialised; 52 | }; -------------------------------------------------------------------------------- /src/hardware/fram.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "fram.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | FRAM::FRAM() 22 | : LOG("F-Ram"), 23 | fm24c04_() 24 | //------------------------------------------------------------------------------ 25 | {} 26 | 27 | //------------------------------------------------------------------------------ 28 | bool FRAM::begin() 29 | //------------------------------------------------------------------------------ 30 | { 31 | return fm24c04_.begin(); 32 | } 33 | 34 | //------------------------------------------------------------------------------ 35 | void FRAM::setFrequency(uint16_t frequency) 36 | //------------------------------------------------------------------------------ 37 | { 38 | fm24c04_.write16(BASE_ADR_FREQUENCY, frequency); 39 | } 40 | 41 | //------------------------------------------------------------------------------ 42 | uint16_t FRAM::getFrequency() const 43 | //------------------------------------------------------------------------------ 44 | { 45 | return fm24c04_.read16(BASE_ADR_FREQUENCY); 46 | } 47 | 48 | //------------------------------------------------------------------------------ 49 | void FRAM::setChannelValue(uint8_t channel, uint16_t value) 50 | //------------------------------------------------------------------------------ 51 | { 52 | if (channel >= MAX_CHANNEL_COUNT) { 53 | channel = MAX_CHANNEL_COUNT - 1; 54 | } 55 | fm24c04_.write16(BASE_ADR_CHANNELVALUES + channel + channel, value); 56 | } 57 | 58 | //------------------------------------------------------------------------------ 59 | uint16_t FRAM::getChannelValue(uint8_t channel) const 60 | //------------------------------------------------------------------------------ 61 | { 62 | if (channel >= MAX_CHANNEL_COUNT) { 63 | channel = MAX_CHANNEL_COUNT - 1; 64 | } 65 | return fm24c04_.read16(BASE_ADR_CHANNELVALUES + channel + channel); 66 | } 67 | 68 | //------------------------------------------------------------------------------ 69 | void FRAM::recalculateCRC() 70 | //------------------------------------------------------------------------------ 71 | { 72 | uint32_t crc = fm24c04_.calcChecksum(DATA_START_ADR, DATA_END_ADR); 73 | fm24c04_.write32(BASE_ADR_CRC, crc); 74 | } 75 | 76 | //------------------------------------------------------------------------------ 77 | bool FRAM::validateCRC() 78 | //------------------------------------------------------------------------------ 79 | { 80 | return fm24c04_.validate(DATA_START_ADR, DATA_END_ADR, BASE_ADR_CRC); 81 | } 82 | 83 | // //------------------------------------------------------------------------------ 84 | // void FRAM::dump() 85 | // //------------------------------------------------------------------------------ 86 | // { 87 | // LOG.d("[FRAM] dump"); 88 | // LOG.d(" from: %d to %d", DATA_START_ADR, COMPLETE_END_ADR); 89 | // bool first = true; 90 | // for (uint8_t i = DATA_START_ADR; i <= COMPLETE_END_ADR; ++i) { 91 | // if (first || i % 8 == 0) { 92 | // first = false; 93 | // Serial.printf("\n 0x%.4x ", i); 94 | // } else { 95 | // if (i % 4 == 0) { 96 | // Serial.print(" "); 97 | // } 98 | // } 99 | // Serial.printf("%.2x ", fm24c04_.read8(i)); 100 | // } 101 | // Serial.println(); 102 | // } -------------------------------------------------------------------------------- /src/hardware/fram.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include "fm24c04.h" 23 | #include "util/logger.h" 24 | 25 | class FRAM { 26 | public: 27 | static const uint8_t MAX_CHANNEL_COUNT = 16; 28 | static const uint16_t DATA_START_ADR = 0; 29 | static const uint16_t BASE_ADR_FREQUENCY = DATA_START_ADR; 30 | static const uint16_t BASE_ADR_CHANNELVALUES = BASE_ADR_FREQUENCY + 2; 31 | static const uint16_t BASE_ADR_CRC = BASE_ADR_CHANNELVALUES + (MAX_CHANNEL_COUNT * 2); 32 | static const uint16_t DATA_END_ADR = BASE_ADR_CRC - 1; 33 | static const uint16_t COMPLETE_END_ADR = BASE_ADR_CRC + 3; 34 | 35 | FRAM(); 36 | bool begin(); 37 | void setFrequency(uint16_t frequency); 38 | uint16_t getFrequency() const; 39 | void setChannelValue(uint8_t channel, uint16_t value); 40 | uint16_t getChannelValue(uint8_t channel) const; 41 | void recalculateCRC(); 42 | bool validateCRC(); 43 | // void dump(); 44 | 45 | private: 46 | Logger LOG; 47 | FM24C04 fm24c04_; 48 | }; -------------------------------------------------------------------------------- /src/hardware/pwm.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "pwm.h" 19 | #include "../util/dimcurve.h" 20 | 21 | //------------------------------------------------------------------------------ 22 | PWM::PWM() 23 | : faboPWM_() 24 | //------------------------------------------------------------------------------ 25 | {} 26 | 27 | //------------------------------------------------------------------------------ 28 | bool PWM::begin() 29 | //------------------------------------------------------------------------------ 30 | { 31 | bool result = faboPWM_.begin(); 32 | if (result) { 33 | faboPWM_.init(0); 34 | } 35 | return result; 36 | } 37 | 38 | //------------------------------------------------------------------------------ 39 | uint16_t PWM::setFrequency(uint16_t frequency) 40 | //------------------------------------------------------------------------------ 41 | { 42 | if (frequency < 24) frequency = 24; 43 | if (frequency > 1526) frequency = 1526; 44 | faboPWM_.set_hz(frequency); 45 | return frequency; 46 | } 47 | 48 | //------------------------------------------------------------------------------ 49 | void PWM::setChannelValue(uint8_t channel, uint16_t value) 50 | //------------------------------------------------------------------------------ 51 | { 52 | if (channel > 15) channel = 15; 53 | if (value > 4095) value = 4095; 54 | faboPWM_.set_channel_value(channel, value); 55 | } 56 | 57 | //------------------------------------------------------------------------------ 58 | void PWM::setChannelValue8(uint8_t channel, uint8_t value) 59 | //------------------------------------------------------------------------------ 60 | { 61 | if (channel > 15) return; 62 | faboPWM_.set_channel_value(channel, value); 63 | } 64 | 65 | //------------------------------------------------------------------------------ 66 | void PWM::setChannelValueLog(uint8_t channel, uint8_t value) 67 | //------------------------------------------------------------------------------ 68 | { 69 | if (channel > 15) return; 70 | faboPWM_.set_channel_value(channel, pwmtable_16[value]); 71 | } -------------------------------------------------------------------------------- /src/hardware/pwm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include 23 | 24 | class PWM { 25 | public: 26 | PWM(); 27 | bool begin(); 28 | uint16_t setFrequency(uint16_t frequency); 29 | void setChannelValue(uint8_t channel, uint16_t value); 30 | void setChannelValue8(uint8_t channel, uint8_t value); 31 | void setChannelValueLog(uint8_t channel, uint8_t value); 32 | 33 | private: 34 | FaBoPWM faboPWM_; 35 | }; -------------------------------------------------------------------------------- /src/hardware/statusled.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "statusled.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | StatusLed::StatusLed(uint8_t pin, uint8_t channel, double frequency) 22 | : pin_(pin), 23 | channel_(channel), 24 | frequency_(frequency), 25 | nextSwitch_(0), 26 | status_(OFF) 27 | //------------------------------------------------------------------------------ 28 | {} 29 | 30 | //------------------------------------------------------------------------------ 31 | bool StatusLed::begin() 32 | //------------------------------------------------------------------------------ 33 | { 34 | pinMode(pin_, OUTPUT); 35 | ledcSetup(channel_, frequency_, 8); 36 | ledcAttachPin(pin_, channel_); 37 | return true; 38 | } 39 | 40 | //------------------------------------------------------------------------------ 41 | void StatusLed::on() 42 | //------------------------------------------------------------------------------ 43 | { 44 | status_ = ON; 45 | ledcWrite(channel_, 255); 46 | } 47 | 48 | //------------------------------------------------------------------------------ 49 | void StatusLed::off() 50 | //------------------------------------------------------------------------------ 51 | { 52 | status_ = OFF; 53 | ledcWrite(channel_, 0); 54 | } 55 | 56 | //------------------------------------------------------------------------------ 57 | void StatusLed::blink(double duty) 58 | //------------------------------------------------------------------------------ 59 | { 60 | ledcWrite(channel_, (uint32_t)(duty * 255)); 61 | } 62 | 63 | //------------------------------------------------------------------------------ 64 | void StatusLed::setFrequency(double frequency) 65 | //------------------------------------------------------------------------------ 66 | { 67 | frequency_ = frequency; 68 | ledcSetup(channel_, frequency_, 8); 69 | } 70 | 71 | //------------------------------------------------------------------------------ 72 | double StatusLed::getFrequency() const 73 | //------------------------------------------------------------------------------ 74 | { 75 | return frequency_; 76 | } 77 | 78 | //------------------------------------------------------------------------------ 79 | void StatusLed::alternate() 80 | //------------------------------------------------------------------------------ 81 | { 82 | if (status_ == BLINK_ON) { 83 | status_ = BLINK_OFF; 84 | digitalWrite(pin_, false); 85 | } else if (status_ == BLINK_OFF) { 86 | status_ = BLINK_ON; 87 | digitalWrite(pin_, true); 88 | } 89 | } -------------------------------------------------------------------------------- /src/hardware/statusled.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include "Arduino.h" 21 | 22 | class StatusLed { 23 | public: 24 | enum ledStatus_t { ON, OFF, BLINK_ON, BLINK_OFF }; 25 | StatusLed(uint8_t pin, uint8_t channel = 0, double frequency = 2); 26 | bool begin(); 27 | void on(); 28 | void off(); 29 | void blink(double duty = 0.5); 30 | void setFrequency(double frequency); 31 | double getFrequency() const; 32 | void alternate(); 33 | 34 | private: 35 | uint8_t pin_; 36 | uint8_t channel_; 37 | double frequency_; 38 | uint64_t nextSwitch_; 39 | ledStatus_t status_; 40 | }; 41 | -------------------------------------------------------------------------------- /src/net/artnet.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "artnet.h" 3 | 4 | //------------------------------------------------------------------------------ 5 | Artnet::Artnet() 6 | : LOG("Artnet"), 7 | e131(UNIVERSE_COUNT), 8 | timeoutUs_(0), 9 | lastPaketTimestampUs_(0), 10 | dataCallback_(NULL), 11 | timeoutCallback_(NULL) 12 | //------------------------------------------------------------------------------ 13 | {} 14 | 15 | //------------------------------------------------------------------------------ 16 | bool Artnet::begin(uint16_t universe, uint64_t timeoutUs) 17 | //------------------------------------------------------------------------------ 18 | { 19 | timeoutUs_ = timeoutUs; 20 | return e131.begin(E131_MULTICAST, universe, UNIVERSE_COUNT); 21 | } 22 | 23 | //------------------------------------------------------------------------------ 24 | void Artnet::loop() 25 | //------------------------------------------------------------------------------ 26 | { 27 | uint64_t now = esp_timer_get_time(); 28 | if (!e131.isEmpty()) { 29 | lastPaketTimestampUs_ = now; 30 | e131_packet_t packet; 31 | e131.pull(&packet); // Pull packet from ring buffer 32 | if (dataCallback_) { 33 | dataCallback_(&packet.property_values[1], htons(packet.property_value_count) - 1); 34 | } 35 | } 36 | 37 | if (timeoutCallback_ && lastPaketTimestampUs_ && lastPaketTimestampUs_ + timeoutUs_ < now) { 38 | lastPaketTimestampUs_ = 0; 39 | timeoutCallback_(); 40 | } 41 | } 42 | 43 | //------------------------------------------------------------------------------ 44 | void Artnet::onData(DataCallback cb) 45 | //------------------------------------------------------------------------------ 46 | { 47 | dataCallback_ = cb; 48 | } 49 | 50 | //------------------------------------------------------------------------------ 51 | void Artnet::onTimeout(TimeoutCallback cb) 52 | //------------------------------------------------------------------------------ 53 | { 54 | timeoutCallback_ = cb; 55 | } 56 | -------------------------------------------------------------------------------- /src/net/artnet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "util/logger.h" 25 | 26 | #define UNIVERSE_COUNT 1 // Total number of Universes to listen for, starting at UNIVERSE 27 | 28 | // thats why i hate c++ 29 | // many thx to https://stackoverflow.com/questions/1000663/using-a-c-class-member-function-as-a-c-callback-function/56943930#56943930 30 | template 31 | struct Callback; 32 | 33 | template 34 | struct Callback { 35 | template 36 | static Ret callback(Args... args) { 37 | return func(args...); 38 | } 39 | static std::function func; 40 | }; 41 | 42 | template 43 | std::function Callback::func; 44 | 45 | /** 46 | * https://en.wikipedia.org/wiki/Art-Net 47 | */ 48 | class Artnet { 49 | public: 50 | typedef void (*callback_t)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data); 51 | typedef void (*DataCallback)(uint8_t* data, uint16_t length); 52 | typedef void (*TimeoutCallback)(); 53 | 54 | Artnet(); 55 | bool begin(uint16_t universe, uint64_t timeoutUs = 5000*1000); // default timeout: 5s 56 | void loop(); 57 | void onData(DataCallback cb); 58 | void onTimeout(TimeoutCallback cb); 59 | 60 | private: 61 | Logger LOG; 62 | ESPAsyncE131 e131; 63 | uint64_t timeoutUs_; 64 | uint64_t lastPaketTimestampUs_; 65 | DataCallback dataCallback_; 66 | TimeoutCallback timeoutCallback_; 67 | }; -------------------------------------------------------------------------------- /src/net/mqtt.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "mqtt.h" 19 | #include "util/jsonparser.h" 20 | 21 | //------------------------------------------------------------------------------ 22 | Mqtt::Mqtt() 23 | : wifiClient_(), 24 | pubSubClient_(wifiClient_) 25 | //------------------------------------------------------------------------------ 26 | {} 27 | 28 | //------------------------------------------------------------------------------ 29 | bool Mqtt::begin() { return true; } 30 | 31 | //------------------------------------------------------------------------------ 32 | void Mqtt::loop() 33 | //------------------------------------------------------------------------------ 34 | { 35 | pubSubClient_.loop(); 36 | } 37 | 38 | //------------------------------------------------------------------------------ 39 | bool Mqtt::connect(const String& id, const String& topic, const String& server, uint16_t port, const String& userName, 40 | const String& password) 41 | //------------------------------------------------------------------------------ 42 | { 43 | if (pubSubClient_.connected()) { 44 | pubSubClient_.disconnect(); 45 | } 46 | 47 | // prevent consequential error due to dns requests with empty string 48 | if (server.isEmpty()) { 49 | Serial.println("[MQTT] Error: no server specified. Can not connect"); 50 | return false; 51 | } 52 | 53 | pubSubClient_.setServer(server.c_str(), port); 54 | 55 | using namespace std; 56 | using namespace std::placeholders; 57 | pubSubClient_.setCallback(bind(&Mqtt::callback_, this, _1, _2, _3)); 58 | 59 | if (pubSubClient_.connect(id.c_str(), (topic + "/alive").c_str(), 2, true, "false")) { 60 | pubSubClient_.publish((topic + "/alive").c_str(), "true"); 61 | pubSubClient_.subscribe((topic + "/set").c_str()); 62 | } 63 | return pubSubClient_.connected(); 64 | } 65 | 66 | //------------------------------------------------------------------------------ 67 | bool Mqtt::disconnect() 68 | //------------------------------------------------------------------------------ 69 | { 70 | pubSubClient_.disconnect(); 71 | return pubSubClient_.connected(); 72 | } 73 | 74 | //------------------------------------------------------------------------------ 75 | bool Mqtt::isConnected() 76 | //------------------------------------------------------------------------------ 77 | { 78 | return pubSubClient_.connected(); 79 | } 80 | 81 | //------------------------------------------------------------------------------ 82 | void Mqtt::onData(SubscriptionResponseFunction_t f) 83 | //------------------------------------------------------------------------------ 84 | { 85 | cb_ = f; 86 | } 87 | 88 | //------------------------------------------------------------------------------ 89 | void Mqtt::callback_(char* topic, byte* payload, unsigned int length) 90 | //------------------------------------------------------------------------------ 91 | { 92 | DynamicJsonDocument doc(256); 93 | DeserializationError error = deserializeJson(doc, payload, length); 94 | if (error) { 95 | Serial.print(F("deserializeJson() failed: ")); 96 | Serial.println(error.c_str()); 97 | return; 98 | } 99 | 100 | if (cb_ != NULL) { 101 | cb_(doc); 102 | } 103 | } 104 | 105 | //------------------------------------------------------------------------------ 106 | void Mqtt::publish(const String& topic, const String& payload) 107 | //------------------------------------------------------------------------------ 108 | { 109 | pubSubClient_.publish(topic.c_str(), payload.c_str()); 110 | } 111 | 112 | //------------------------------------------------------------------------------ 113 | void Mqtt::publish(const String& topic, const JsonDocument& doc, bool pretty) 114 | //------------------------------------------------------------------------------ 115 | { 116 | String json; 117 | JsonParser::doc2String(doc, json, pretty); 118 | pubSubClient_.publish(topic.c_str(), json.c_str()); 119 | } -------------------------------------------------------------------------------- /src/net/mqtt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | class Mqtt { 28 | public: 29 | typedef std::function SubscriptionResponseFunction_t; 30 | 31 | Mqtt(); 32 | 33 | bool begin(); 34 | 35 | void loop(); 36 | 37 | bool connect(const String& id, const String& topic, const String& server, uint16_t port, const String& userName, const String& password); 38 | bool disconnect(); 39 | bool isConnected(); 40 | void onData(SubscriptionResponseFunction_t f); 41 | void publish(const String& topic, const String& json); 42 | void publish(const String& topic, const JsonDocument& doc, bool pretty); 43 | 44 | private: 45 | void callback_(char* topic, byte* payload, unsigned int length); 46 | WiFiClient wifiClient_; 47 | PubSubClient pubSubClient_; 48 | SubscriptionResponseFunction_t cb_; 49 | }; -------------------------------------------------------------------------------- /src/net/ota.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "ota.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | //------------------------------------------------------------------------------ 26 | OTA::OTA() 27 | : LOG("OTA"), 28 | isUpdating_(false) 29 | //------------------------------------------------------------------------------ 30 | {} 31 | 32 | //------------------------------------------------------------------------------ 33 | bool OTA::begin() 34 | //------------------------------------------------------------------------------ 35 | { 36 | ArduinoOTA 37 | .onStart([&]() { 38 | if (startCallback_) { 39 | startCallback_(); 40 | } 41 | 42 | isUpdating_ = true; 43 | String type; 44 | if (ArduinoOTA.getCommand() == U_FLASH) { 45 | type = "sketch"; 46 | } else { 47 | type = "filesystem"; 48 | SPIFFS.end(); 49 | } 50 | Serial.println("[OTA] Start updating " + type); 51 | }) 52 | .onEnd([&]() { 53 | if (endCallback_) { 54 | endCallback_(); 55 | } 56 | 57 | Serial.println("\nEnd"); 58 | isUpdating_ = false; 59 | }) 60 | .onProgress([&](unsigned int progress, unsigned int total) { 61 | if (progressCallback_) { 62 | progressCallback_((double)progress/(double)total); 63 | } 64 | 65 | Serial.printf("[OTA] Progress: %u%%\r", (progress / (total / 100))); 66 | }) 67 | .onError([=](ota_error_t error) { 68 | isUpdating_ = false; 69 | Serial.printf("[OTA] Error[%u]: ", error); 70 | if (error == OTA_AUTH_ERROR) 71 | Serial.println("[OTA] Auth Failed"); 72 | else if (error == OTA_BEGIN_ERROR) 73 | Serial.println("[OTA] Begin Failed"); 74 | else if (error == OTA_CONNECT_ERROR) 75 | Serial.println("[OTA] Connect Failed"); 76 | else if (error == OTA_RECEIVE_ERROR) 77 | Serial.println("[OTA] Receive Failed"); 78 | else if (error == OTA_END_ERROR) 79 | Serial.println("[OTA] End Failed"); 80 | }); 81 | 82 | ArduinoOTA.begin(); 83 | return true; 84 | } 85 | 86 | //------------------------------------------------------------------------------ 87 | void OTA::loop() 88 | //------------------------------------------------------------------------------ 89 | { 90 | ArduinoOTA.handle(); 91 | } 92 | 93 | //------------------------------------------------------------------------------ 94 | bool OTA::isUpdating() 95 | //------------------------------------------------------------------------------ 96 | { 97 | return isUpdating_; 98 | } 99 | 100 | //------------------------------------------------------------------------------ 101 | void OTA::onStart(StartEndCallback cb) 102 | //------------------------------------------------------------------------------ 103 | {} 104 | //------------------------------------------------------------------------------ 105 | void OTA::onEnd(StartEndCallback cb) 106 | //------------------------------------------------------------------------------ 107 | {} 108 | //------------------------------------------------------------------------------ 109 | void OTA::onProgress(ProgressCallback cb) 110 | //------------------------------------------------------------------------------ 111 | {} -------------------------------------------------------------------------------- /src/net/ota.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include "Arduino.h" 21 | 22 | #include "util/logger.h" 23 | 24 | class OTA { 25 | public: 26 | typedef void (*StartEndCallback)(); 27 | typedef void (*ProgressCallback)(double uploaded); 28 | 29 | OTA(); 30 | bool begin(); 31 | void loop(); 32 | 33 | bool isUpdating(); 34 | 35 | void onStart(StartEndCallback cb); 36 | void onEnd(StartEndCallback cb); 37 | void onProgress(ProgressCallback cb); 38 | 39 | private: 40 | Logger LOG; 41 | bool isUpdating_; 42 | StartEndCallback startCallback_; 43 | StartEndCallback endCallback_; 44 | ProgressCallback progressCallback_; 45 | }; -------------------------------------------------------------------------------- /src/net/reconnector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "reconnector.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | Reconnector::Reconnector() 22 | : LOG("Reconnector"), 23 | configured_(false), 24 | delayMs_(2000), 25 | timer_(0), 26 | tryToConnectCB_(NULL), 27 | tryToDisconnectCB_(NULL), 28 | isConnectedCB_(NULL), 29 | state_(DISCONNECTED), 30 | stateNames_{"DISCONNECTED", "DISCONNECTION_REQUEST", "CONNECTION_REQUEST", "CONNECTION_REQUEST_DELAYED", "CONNECTED"} 31 | //------------------------------------------------------------------------------ 32 | {} 33 | 34 | //------------------------------------------------------------------------------ 35 | bool Reconnector::begin(uint64_t delayMs) 36 | //------------------------------------------------------------------------------ 37 | { 38 | delayMs_ = delayMs; 39 | return true; 40 | } 41 | 42 | //------------------------------------------------------------------------------ 43 | void Reconnector::tryToConnect(bool (*cb)()) 44 | //------------------------------------------------------------------------------ 45 | { 46 | tryToConnectCB_ = cb; 47 | } 48 | 49 | //------------------------------------------------------------------------------ 50 | void Reconnector::tryToDisconnect(bool (*cb)()) 51 | //------------------------------------------------------------------------------ 52 | { 53 | tryToDisconnectCB_ = cb; 54 | } 55 | 56 | //------------------------------------------------------------------------------ 57 | void Reconnector::isConnected(bool (*cb)()) 58 | //------------------------------------------------------------------------------ 59 | { 60 | isConnectedCB_ = cb; 61 | } 62 | 63 | //------------------------------------------------------------------------------ 64 | void Reconnector::onWifiConnected() 65 | //------------------------------------------------------------------------------ 66 | { 67 | setState(CONNECTION_REQUEST); 68 | } 69 | 70 | //------------------------------------------------------------------------------ 71 | void Reconnector::onWifiDisconnected() 72 | //------------------------------------------------------------------------------ 73 | { 74 | switch (state_) { 75 | case DISCONNECTED: 76 | case DISCONNECTION_REQUEST: 77 | break; 78 | 79 | case CONNECTION_REQUEST: 80 | case CONNECTION_REQUEST_DELAYED: 81 | setState(DISCONNECTED); 82 | break; 83 | 84 | case CONNECTED: 85 | setState(DISCONNECTION_REQUEST); 86 | break; 87 | } 88 | } 89 | 90 | //------------------------------------------------------------------------------ 91 | void Reconnector::onMQTTConfigured() 92 | //------------------------------------------------------------------------------ 93 | { 94 | configured_ = true; 95 | } 96 | 97 | //------------------------------------------------------------------------------ 98 | void Reconnector::loop() 99 | //------------------------------------------------------------------------------ 100 | { 101 | switch (state_) { 102 | case DISCONNECTED: 103 | break; 104 | 105 | case CONNECTION_REQUEST: 106 | if (configured_) { 107 | onMqttConnectionRequest(); 108 | } 109 | break; 110 | 111 | case DISCONNECTION_REQUEST: 112 | onMqttDisconnectionRequest(); 113 | break; 114 | 115 | case CONNECTION_REQUEST_DELAYED: 116 | if (timer_ < millis()) { 117 | onMqttConnectionRequest(); 118 | } 119 | break; 120 | 121 | case CONNECTED: 122 | if (!onIsConnectedRequest()) { 123 | setState(CONNECTION_REQUEST_DELAYED); // prevent fast reconnection loop 124 | timer_ = millis() + delayMs_; 125 | } 126 | break; 127 | } 128 | } 129 | 130 | //------------------------------------------------------------------------------ 131 | const String& Reconnector::getStateName(ReconnectorState state) const 132 | //------------------------------------------------------------------------------ 133 | { 134 | return stateNames_[state]; 135 | } 136 | 137 | //------------------------------------------------------------------------------ 138 | Reconnector::ReconnectorState Reconnector::getState() const 139 | //------------------------------------------------------------------------------ 140 | { 141 | return state_; 142 | } 143 | 144 | //------------------------------------------------------------------------------ 145 | void Reconnector::setState(ReconnectorState newState) 146 | //------------------------------------------------------------------------------ 147 | { 148 | if (state_ != newState) { 149 | LOG.d("state changed from '%s' to '%s'\n", getStateName(state_).c_str(), getStateName(newState).c_str()); 150 | state_ = newState; 151 | } 152 | } 153 | 154 | //------------------------------------------------------------------------------ 155 | bool Reconnector::onMqttConnectionRequest() 156 | //------------------------------------------------------------------------------ 157 | { 158 | bool result = false; 159 | if (tryToConnectCB_ != NULL) { 160 | result = tryToConnectCB_(); 161 | } 162 | 163 | if (result) { 164 | setState(CONNECTED); 165 | } else { 166 | setState(CONNECTION_REQUEST_DELAYED); 167 | timer_ = millis() + delayMs_; 168 | } 169 | 170 | return result; 171 | } 172 | 173 | //------------------------------------------------------------------------------ 174 | bool Reconnector::onMqttDisconnectionRequest() 175 | //------------------------------------------------------------------------------ 176 | { 177 | bool result = false; 178 | if (tryToDisconnectCB_ != NULL) { 179 | result = tryToDisconnectCB_(); 180 | } 181 | 182 | setState(DISCONNECTED); 183 | 184 | return result; 185 | } 186 | 187 | //------------------------------------------------------------------------------ 188 | bool Reconnector::onIsConnectedRequest() 189 | //------------------------------------------------------------------------------ 190 | { 191 | bool result = true; 192 | if (isConnectedCB_ != NULL) { 193 | result = isConnectedCB_(); 194 | } 195 | return result; 196 | } 197 | -------------------------------------------------------------------------------- /src/net/reconnector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include "util/logger.h" 23 | 24 | class Reconnector { 25 | public: 26 | typedef enum { 27 | DISCONNECTED = 0, 28 | DISCONNECTION_REQUEST, 29 | CONNECTION_REQUEST, 30 | CONNECTION_REQUEST_DELAYED, 31 | CONNECTED 32 | } ReconnectorState; 33 | 34 | Reconnector(); 35 | 36 | bool begin(uint64_t delayMs = 2000); 37 | 38 | void tryToConnect(bool (*cb)()); 39 | void tryToDisconnect(bool (*cb)()); 40 | void isConnected(bool (*cb)()); 41 | 42 | void onWifiConnected(); 43 | void onWifiDisconnected(); 44 | void onMQTTConfigured(); 45 | 46 | void loop(); 47 | 48 | ReconnectorState getState() const; 49 | const String& getStateName(ReconnectorState state) const; 50 | 51 | private: 52 | void setState(ReconnectorState newState); 53 | bool onMqttConnectionRequest(); 54 | bool onMqttDisconnectionRequest(); 55 | bool onIsConnectedRequest(); 56 | 57 | Logger LOG; 58 | bool configured_; 59 | uint64_t delayMs_; 60 | uint64_t timer_; 61 | bool (*tryToConnectCB_)(); 62 | bool (*tryToDisconnectCB_)(); 63 | bool (*isConnectedCB_)(); 64 | ReconnectorState state_; 65 | const String stateNames_[5]; 66 | }; -------------------------------------------------------------------------------- /src/net/websocket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "websocket.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | Websocket::Websocket() 22 | : initialized_(false), 23 | wsServer_(NULL), 24 | onStatusRequest_(NULL), 25 | onData_(NULL) 26 | //------------------------------------------------------------------------------ 27 | {} 28 | 29 | //------------------------------------------------------------------------------ 30 | Websocket::~Websocket() 31 | //------------------------------------------------------------------------------ 32 | { 33 | if (!initialized_) { 34 | wsServer_->close(); 35 | delete wsServer_; 36 | } 37 | } 38 | 39 | //------------------------------------------------------------------------------ 40 | bool Websocket::begin(uint16_t port) 41 | //------------------------------------------------------------------------------ 42 | { 43 | using namespace std; 44 | using namespace std::placeholders; 45 | 46 | if (!initialized_) { 47 | wsServer_ = new WebSocketsServer(port); 48 | wsServer_->begin(); 49 | wsServer_->onEvent(bind(&Websocket::onWsEvent, this, _1, _2, _3, _4)); 50 | initialized_ = true; 51 | } 52 | return initialized_; 53 | } 54 | 55 | //------------------------------------------------------------------------------ 56 | void Websocket::loop() 57 | //------------------------------------------------------------------------------ 58 | { 59 | if (initialized_) { 60 | wsServer_->loop(); 61 | } 62 | } 63 | 64 | //------------------------------------------------------------------------------ 65 | void Websocket::send(String& data) 66 | //------------------------------------------------------------------------------ 67 | { 68 | if (initialized_) { 69 | wsServer_->broadcastTXT(data); 70 | } 71 | } 72 | 73 | //------------------------------------------------------------------------------ 74 | void Websocket::send(uint8_t clientId, String& data) 75 | //------------------------------------------------------------------------------ 76 | { 77 | if (initialized_) { 78 | wsServer_->sendTXT(clientId, data); 79 | } 80 | } 81 | 82 | //------------------------------------------------------------------------------ 83 | void Websocket::onStatusRequest(onStatusRequest_t cb) 84 | //------------------------------------------------------------------------------ 85 | { 86 | onStatusRequest_ = cb; 87 | } 88 | 89 | //------------------------------------------------------------------------------ 90 | void Websocket::onData(onData_t cb) 91 | //------------------------------------------------------------------------------ 92 | { 93 | onData_ = cb; 94 | } 95 | 96 | //------------------------------------------------------------------------------ 97 | void Websocket::onWsEvent(uint8_t clientId, WStype_t type, uint8_t* payload, size_t length) 98 | //------------------------------------------------------------------------------ 99 | { 100 | switch (type) { 101 | case WStype_DISCONNECTED: 102 | Serial.printf("[%u] Disconnected!\n", clientId); 103 | break; 104 | case WStype_CONNECTED: { 105 | IPAddress ip = wsServer_->remoteIP(clientId); 106 | Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", clientId, ip[0], ip[1], ip[2], ip[3], payload); 107 | if (onStatusRequest_) { 108 | onStatusRequest_(clientId); 109 | } 110 | } break; 111 | case WStype_TEXT: 112 | if (onData_) { 113 | String data((char*)payload); 114 | onData_(clientId, data); 115 | } 116 | break; 117 | case WStype_BIN: 118 | case WStype_ERROR: 119 | case WStype_FRAGMENT_TEXT_START: 120 | case WStype_FRAGMENT_BIN_START: 121 | case WStype_FRAGMENT: 122 | case WStype_FRAGMENT_FIN: 123 | break; 124 | } 125 | } -------------------------------------------------------------------------------- /src/net/websocket.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 4 | * Copyright (c) 2019 Lars Brandt. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, version 3. 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include 24 | 25 | class Websocket { 26 | public: 27 | typedef void (*onStatusRequest_t)(const uint8_t clientId); 28 | typedef void (*onData_t)(const uint8_t clientId, const String& json); 29 | 30 | //------------------------------------------------------------------------------ 31 | Websocket(); 32 | ~Websocket(); 33 | bool begin(uint16_t port); 34 | void loop(); 35 | void send(String& data); 36 | void send(uint8_t clientId, String& data); 37 | void onStatusRequest(onStatusRequest_t cb); 38 | void onData(onData_t cb); 39 | 40 | private: 41 | void onWsEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length); 42 | 43 | bool initialized_; 44 | WebSocketsServer* wsServer_; 45 | onStatusRequest_t onStatusRequest_; 46 | onData_t onData_; 47 | }; -------------------------------------------------------------------------------- /src/net/wifistate.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "wifistate.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | const String WiFiState::eventNames_[28]{"SYSTEM_EVENT_WIFI_READY", 22 | "SYSTEM_EVENT_SCAN_DONE", 23 | "SYSTEM_EVENT_STA_START", 24 | "SYSTEM_EVENT_STA_STOP", 25 | "SYSTEM_EVENT_STA_CONNECTED", 26 | "SYSTEM_EVENT_STA_DISCONNECTED", 27 | "SYSTEM_EVENT_STA_AUTHMODE_CHANGE", 28 | "SYSTEM_EVENT_STA_GOT_IP", 29 | "SYSTEM_EVENT_STA_LOST_IP", 30 | "SYSTEM_EVENT_STA_WPS_ER_SUCCESS", 31 | "SYSTEM_EVENT_STA_WPS_ER_FAILED", 32 | "SYSTEM_EVENT_STA_WPS_ER_TIMEOUT", 33 | "SYSTEM_EVENT_STA_WPS_ER_PIN", 34 | "SYSTEM_EVENT_STA_WPS_ER_PBC_OVERLAP", 35 | "SYSTEM_EVENT_AP_START", 36 | "SYSTEM_EVENT_AP_STOP", 37 | "SYSTEM_EVENT_AP_STACONNECTED", 38 | "SYSTEM_EVENT_AP_STADISCONNECTED", 39 | "SYSTEM_EVENT_AP_STAIPASSIGNED", 40 | "SYSTEM_EVENT_AP_PROBEREQRECVED", 41 | "SYSTEM_EVENT_GOT_IP6", 42 | "SYSTEM_EVENT_ETH_START", 43 | "SYSTEM_EVENT_ETH_STOP", 44 | "SYSTEM_EVENT_ETH_CONNECTED", 45 | "SYSTEM_EVENT_ETH_DISCONNECTED", 46 | "SYSTEM_EVENT_ETH_GOT_IP", 47 | "SYSTEM_EVENT_MAX", 48 | "UNKNOWN"}; 49 | 50 | //------------------------------------------------------------------------------ 51 | const String WiFiState::statusNames_[9]{"WL_IDLE_STATUS", "WL_NO_SSID_AVAIL", "WL_SCAN_COMPLETED", "WL_CONNECTED", "WL_CONNECT_FAILED", 52 | "WL_CONNECTION_LOST", "WL_DISCONNECTED", "WL_NO_SHIELD", "UNKNOWN"}; 53 | 54 | //------------------------------------------------------------------------------ 55 | WiFiState::WiFiState() 56 | : LOG("WiFiState"), 57 | cbConnect_(NULL), 58 | cbDisconnect_(NULL) 59 | //------------------------------------------------------------------------------ 60 | { 61 | // WiFi.onEvent(std::bind(&WiFiState::onWifiEvent, this, std::placeholders::_1, this, std::placeholders::_2)); 62 | WiFi.onEvent(std::bind(&WiFiState::onWifiEvent, this, std::placeholders::_1, std::placeholders::_2)); 63 | } 64 | 65 | //------------------------------------------------------------------------------ 66 | void WiFiState::onWifiEvent(WiFiEvent_t event, WiFiEventInfo_t info) 67 | //------------------------------------------------------------------------------ 68 | { 69 | LOG.d("event: %d - %s", event, getEventName(event).c_str()); 70 | switch (event) { 71 | case SYSTEM_EVENT_STA_GOT_IP: 72 | LOG.d("connected"); 73 | isConnected_ = true; 74 | if (cbConnect_ != NULL) { 75 | cbConnect_(); 76 | } 77 | break; 78 | case SYSTEM_EVENT_STA_DISCONNECTED: 79 | LOG.d("disconnected, Reason: %u", info.wps_fail_reason); 80 | if (cbDisconnect_ != NULL) { 81 | cbDisconnect_(isConnected_, info.wps_fail_reason); 82 | } 83 | isConnected_ = false; 84 | break; 85 | default: 86 | break; 87 | } 88 | } 89 | 90 | //------------------------------------------------------------------------------ 91 | void WiFiState::printStatus() 92 | //------------------------------------------------------------------------------ 93 | { 94 | LOG.i("Status: %d - %s", WiFi.status(), getStatusName(WiFi.status()).c_str()); 95 | } 96 | 97 | //------------------------------------------------------------------------------ 98 | void WiFiState::onConnect(CallbackConnect cb) 99 | //------------------------------------------------------------------------------ 100 | { 101 | cbConnect_ = cb; 102 | } 103 | 104 | //------------------------------------------------------------------------------ 105 | void WiFiState::onDisconnect(CallbackDisconnect cb) 106 | //------------------------------------------------------------------------------ 107 | { 108 | cbDisconnect_ = cb; 109 | } 110 | 111 | //------------------------------------------------------------------------------ 112 | const String& WiFiState::getEventName(WiFiEvent_t idx) const 113 | //------------------------------------------------------------------------------ 114 | { 115 | return eventNames_[idx]; 116 | } 117 | 118 | //------------------------------------------------------------------------------ 119 | const String& WiFiState::getStatusName(wl_status_t status) const 120 | //------------------------------------------------------------------------------ 121 | { 122 | uint8_t idx = (uint8_t)status; 123 | if (idx == 255) { 124 | idx = 8; 125 | } else if (idx > 6 && idx < 255) { 126 | idx = 9; 127 | } 128 | return statusNames_[idx]; 129 | } 130 | 131 | //------------------------------------------------------------------------------ 132 | bool WiFiState::isConnected() const 133 | //------------------------------------------------------------------------------ 134 | { 135 | return isConnected_; 136 | } 137 | -------------------------------------------------------------------------------- /src/net/wifistate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include "util/logger.h" 23 | 24 | class WiFiState { 25 | public: 26 | typedef void (*CallbackConnect)(); 27 | typedef void (*CallbackDisconnect)(bool wasConnected, uint8_t reason); 28 | 29 | WiFiState(); 30 | 31 | void onWifiEvent(WiFiEvent_t event, WiFiEventInfo_t info); 32 | 33 | bool isConnected() const; 34 | void onConnect(CallbackConnect cb); 35 | void onDisconnect(CallbackDisconnect cb); 36 | 37 | const String& getEventName(WiFiEvent_t idx) const; 38 | const String& getStatusName(wl_status_t status) const; 39 | void printStatus(); 40 | 41 | private: 42 | Logger LOG; 43 | static const String eventNames_[28]; 44 | static const String statusNames_[9]; 45 | 46 | CallbackConnect cbConnect_; 47 | CallbackDisconnect cbDisconnect_; 48 | bool isConnected_ = false; 49 | }; 50 | -------------------------------------------------------------------------------- /src/state.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "state.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | State::State() 22 | : LOG("State") 23 | //------------------------------------------------------------------------------ 24 | {} 25 | 26 | //------------------------------------------------------------------------------ 27 | uint16_t State::getFrequency() const 28 | //------------------------------------------------------------------------------ 29 | { 30 | return frequency_; 31 | } 32 | 33 | //------------------------------------------------------------------------------ 34 | void State::setFrequency(uint16_t frequency) 35 | //------------------------------------------------------------------------------ 36 | { 37 | frequency_ = frequency; 38 | } 39 | 40 | //------------------------------------------------------------------------------ 41 | uint16_t State::getChannelValue(uint8_t channel) const 42 | //------------------------------------------------------------------------------ 43 | { 44 | return channels_[channel]; 45 | } 46 | 47 | //------------------------------------------------------------------------------ 48 | void State::setChannelValue(uint8_t channel, uint16_t value) 49 | //------------------------------------------------------------------------------ 50 | { 51 | LOG.d("set [%u] = %u", channel, value); 52 | channels_[channel] = value; 53 | } 54 | 55 | //------------------------------------------------------------------------------ 56 | void State::dump() 57 | //------------------------------------------------------------------------------ 58 | { 59 | LOG.d("frequency: %u; ", frequency_); 60 | for (uint8_t i = 0; i < 16; ++i) { 61 | LOG.d("[%u]: %u; ", i, channels_[i]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/state.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include "util/logger.h" 25 | 26 | class State { 27 | public: 28 | State(); 29 | uint16_t getFrequency() const; 30 | void setFrequency(uint16_t frequency); 31 | 32 | uint16_t getChannelValue(uint8_t channel) const; 33 | void setChannelValue(uint8_t channel, uint16_t value); 34 | void dump(); 35 | 36 | protected: 37 | uint16_t frequency_; 38 | uint16_t channels_[16]; 39 | 40 | private: 41 | Logger LOG; 42 | }; -------------------------------------------------------------------------------- /src/statistic.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "statistic.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | Statistic::Statistic() 22 | : LOG("Statistics"), 23 | lastMeasurementTime_(0), 24 | loopCount_(0), 25 | period_(10000000) 26 | //------------------------------------------------------------------------------ 27 | {} 28 | 29 | //------------------------------------------------------------------------------ 30 | bool Statistic::begin(uint64_t periodMs) 31 | //------------------------------------------------------------------------------ 32 | { 33 | period_ = periodMs * 1000; 34 | lastMeasurementTime_ = esp_timer_get_time() + period_; 35 | return true; 36 | } 37 | 38 | //------------------------------------------------------------------------------ 39 | void Statistic::loop() 40 | //------------------------------------------------------------------------------ 41 | { 42 | ++loopCount_; 43 | if (lastMeasurementTime_ < esp_timer_get_time()) { 44 | printStatistic(); 45 | loopCount_ = 0; 46 | lastMeasurementTime_ = lastMeasurementTime_ + period_; 47 | } 48 | } 49 | 50 | //------------------------------------------------------------------------------ 51 | void Statistic::printStatistic() 52 | //------------------------------------------------------------------------------ 53 | { 54 | uint64_t currentTime = esp_timer_get_time(); 55 | uint64_t lastPeriodTime = lastMeasurementTime_ - period_; 56 | uint64_t delta = currentTime - lastPeriodTime; 57 | uint64_t loopsPerSecond = (loopCount_ * 1000000) / delta; 58 | LOG.i("[STATISTIC] %" PRIu64 " loops in %" PRIu64 "ms (%" PRIu64 " loops/s)", loopCount_, delta, loopsPerSecond); 59 | } 60 | -------------------------------------------------------------------------------- /src/statistic.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | 19 | #include 20 | 21 | #include "util/logger.h" 22 | 23 | class Statistic { 24 | public: 25 | Statistic(); 26 | bool begin(uint64_t periodMs = 10000); 27 | void loop(); 28 | 29 | private: 30 | Logger LOG; 31 | void printStatistic(); 32 | uint64_t lastMeasurementTime_; 33 | uint64_t loopCount_; 34 | uint64_t period_; 35 | }; -------------------------------------------------------------------------------- /src/util/color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | uint8_t rgbToWhite(uint16_t r, uint16_t g, uint16_t b) { 6 | r = r * 30; // 255 = 7650 7 | g = g * 59; // 255 = 15045 8 | b = b * 11; // 255 = 2805 9 | return (r + g + b) / 100; 10 | } -------------------------------------------------------------------------------- /src/util/dimcurve.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // steps: 256, offset: 150, pwmResolution: 4096 6 | const uint16_t pwmtable_16[256] PROGMEM = { 7 | 0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 9, 8 | 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 9 | 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 40, 10 | 41, 42, 44, 45, 46, 48, 49, 51, 52, 54, 55, 57, 59, 60, 62, 64, 11 | 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 94, 96, 12 | 98, 101, 103, 106, 108, 111, 114, 117, 119, 122, 125, 128, 131, 134, 137, 141, 13 | 144, 147, 151, 154, 158, 161, 165, 169, 173, 177, 181, 185, 189, 193, 197, 202, 14 | 206, 211, 216, 220, 225, 230, 235, 241, 246, 251, 257, 263, 268, 274, 280, 286, 15 | 293, 299, 305, 312, 319, 326, 333, 340, 347, 355, 362, 370, 378, 386, 395, 403, 16 | 412, 421, 430, 439, 448, 458, 467, 477, 487, 498, 508, 519, 530, 541, 553, 565, 17 | 577, 589, 601, 614, 627, 640, 654, 667, 681, 696, 710, 725, 741, 756, 772, 788, 18 | 805, 822, 839, 856, 874, 893, 911, 931, 950, 970, 990, 1011, 1032, 1054, 1076, 1098, 19 | 1121, 1144, 1168, 1193, 1218, 1243, 1269, 1295, 1322, 1350, 1378, 1407, 1436, 1466, 1496, 1528, 20 | 1559, 1592, 1625, 1659, 1693, 1728, 1764, 1801, 1838, 1877, 1916, 1955, 1996, 2038, 2080, 2123, 21 | 2167, 2212, 2258, 2305, 2353, 2402, 2451, 2502, 2554, 2607, 2661, 2717, 2773, 2830, 2889, 2949, 22 | 3010, 3073, 3136, 3201, 3268, 3335, 3405, 3475, 3547, 3621, 3696, 3772, 3851, 3930, 4012, 4095 23 | }; -------------------------------------------------------------------------------- /src/util/jsonparser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "jsonparser.h" 19 | 20 | const char* JsonParser::JSON_LIGHT = "light"; 21 | const char* JsonParser::JSON_FREQUENCY = "frequency"; 22 | const char* JsonParser::JSON_DATA = "data"; 23 | const char* JsonParser::JSON_CHANNEL = "channel"; 24 | const char* JsonParser::JSON_CHANNEL_VALUE = "value"; 25 | const char* JsonParser::JSON_CHANNEL_ALL_VALUE = "all"; 26 | 27 | const char* JsonParser::JSON_DEVICE = "device"; 28 | const char* JsonParser::JSON_DEVICE_NAME = "name"; 29 | const char* JsonParser::JSON_DEVICE_IP = "ip"; 30 | 31 | //------------------------------------------------------------------------------ 32 | void JsonParser::parseChannelData(JsonDocument& doc, void (*cb_f)(uint16_t value), void (*cb_c)(uint8_t channel, uint16_t value)) 33 | //------------------------------------------------------------------------------ 34 | { 35 | // frequency 36 | if (doc.containsKey(JSON_FREQUENCY)) { 37 | uint16_t f = doc[JSON_FREQUENCY]; 38 | cb_f(f); 39 | } 40 | 41 | // data 42 | if (doc.containsKey(JSON_DATA)) { 43 | if (doc[JSON_DATA].is()) { 44 | JsonArray array = doc[JSON_DATA]; 45 | for (const JsonObject section : array) { 46 | parseChannelDataSection(section, cb_c); 47 | } 48 | } else if (doc[JSON_DATA].is()) { 49 | parseChannelDataSection(doc[JSON_DATA], cb_c); 50 | } 51 | } 52 | } 53 | 54 | //------------------------------------------------------------------------------ 55 | void JsonParser::parseChannelDataSection(const JsonObject& section, void (*cb_c)(uint8_t channel, uint16_t value)) 56 | //------------------------------------------------------------------------------ 57 | { 58 | if (section.containsKey(JSON_CHANNEL_ALL_VALUE)) { 59 | if (section[JSON_CHANNEL_ALL_VALUE].is()) { 60 | const JsonArray& array = section[JSON_CHANNEL_ALL_VALUE].as(); 61 | uint8_t arrayIndex = 0; 62 | uint8_t size = array.size(); 63 | for (uint8_t i = 0; i < 16; ++i) { 64 | cb_c(i, array[arrayIndex++ % size]); 65 | } 66 | } else if (section[JSON_CHANNEL_ALL_VALUE].is()) { 67 | uint16_t all = section[JSON_CHANNEL_ALL_VALUE]; 68 | for (uint8_t i = 0; i < 16; ++i) { 69 | cb_c(i, all); 70 | } 71 | } 72 | } 73 | 74 | if (section.containsKey(JSON_CHANNEL) && section.containsKey(JSON_CHANNEL_VALUE)) { 75 | // channel 76 | uint8_t c = section[JSON_CHANNEL]; 77 | 78 | // channel value(s) 79 | if (section[JSON_CHANNEL_VALUE].is()) { 80 | const JsonArray& array = section[JSON_CHANNEL_VALUE].as(); 81 | uint8_t l = array.size(); 82 | for (uint8_t i = 0; i < l; i++) { 83 | cb_c(c + i, array[i]); 84 | } 85 | } else if (section[JSON_CHANNEL_VALUE].is()) { 86 | cb_c(c, section[JSON_CHANNEL_VALUE]); 87 | } 88 | } 89 | } 90 | 91 | //------------------------------------------------------------------------------ 92 | void JsonParser::toJson(JsonDocument& result, const State& state, const DeviceData& deviceData) 93 | //------------------------------------------------------------------------------ 94 | { 95 | // device 96 | JsonObject device = result.createNestedObject(JSON_DEVICE); 97 | device[JSON_DEVICE_NAME] = deviceData.name; 98 | device[JSON_DEVICE_IP] = deviceData.ip; 99 | 100 | // light 101 | JsonObject light = result.createNestedObject(JSON_LIGHT); 102 | light[JSON_FREQUENCY] = state.getFrequency(); 103 | JsonObject data = light.createNestedObject(JSON_DATA); 104 | data[JSON_CHANNEL] = 0; 105 | JsonArray values = data.createNestedArray(JSON_CHANNEL_VALUE); 106 | for (uint8_t i = 0; i < 16; ++i) { 107 | values.add(state.getChannelValue(i)); 108 | } 109 | } 110 | 111 | //------------------------------------------------------------------------------ 112 | void JsonParser::doc2String(const JsonDocument& doc, String& result, bool pretty) 113 | //------------------------------------------------------------------------------ 114 | { 115 | if (pretty) { 116 | serializeJsonPretty(doc, result); 117 | } else { 118 | serializeJson(doc, result); 119 | } 120 | } 121 | 122 | //------------------------------------------------------------------------------ 123 | void JsonParser::channelDataToJsonArray(String& result, const String& msgId, const State& state) 124 | //------------------------------------------------------------------------------ 125 | { 126 | const size_t capacity = JSON_ARRAY_SIZE(16) + JSON_OBJECT_SIZE(2); 127 | DynamicJsonDocument doc(capacity); 128 | doc["msgId"] = msgId; 129 | JsonArray data = doc.createNestedArray("data"); 130 | for (uint8_t i = 0; i < 16; ++i) { 131 | data.add(state.getChannelValue(i)); 132 | } 133 | JsonParser::doc2String(doc, result, false); 134 | } 135 | 136 | //------------------------------------------------------------------------------ 137 | bool JsonParser::channelDataFromJsonArray(const String& json, String& msgId, uint16_t result[16]) 138 | //------------------------------------------------------------------------------ 139 | { 140 | const size_t capacity = JSON_ARRAY_SIZE(16) + JSON_OBJECT_SIZE(2) + 60; 141 | DynamicJsonDocument doc(capacity); 142 | 143 | DeserializationError err = deserializeJson(doc, json); 144 | if (err) { 145 | Serial.print(F("deserializeJson() failed: ")); 146 | Serial.println(err.c_str()); 147 | return false; 148 | } else { 149 | msgId = doc["msgId"].as(); 150 | JsonArray data = doc["data"]; 151 | for (uint8_t i = 0; i < 16; ++i) { 152 | result[i] = data[i]; 153 | } 154 | return true; 155 | } 156 | } 157 | 158 | //------------------------------------------------------------------------------ 159 | void JsonParser::channelDataResponse(String& json, String& msgId) 160 | //------------------------------------------------------------------------------ 161 | { 162 | const size_t capacity = JSON_OBJECT_SIZE(1); 163 | DynamicJsonDocument doc(capacity); 164 | 165 | doc["msgId"] = msgId; 166 | 167 | serializeJson(doc, json); 168 | } 169 | -------------------------------------------------------------------------------- /src/util/jsonparser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include "state.h" 23 | 24 | class JsonParser { 25 | public: 26 | static const char* JSON_LIGHT; 27 | static const char* JSON_FREQUENCY; 28 | static const char* JSON_DATA; 29 | static const char* JSON_CHANNEL; 30 | static const char* JSON_CHANNEL_VALUE; 31 | static const char* JSON_CHANNEL_ALL_VALUE; 32 | static const char* JSON_DEVICE; 33 | static const char* JSON_DEVICE_NAME; 34 | static const char* JSON_DEVICE_IP; 35 | 36 | struct DeviceData { 37 | String name; 38 | String ip; 39 | }; 40 | 41 | static void parseChannelData(JsonDocument& doc, void (*cb_f)(uint16_t value), void (*cb_c)(uint8_t channel, uint16_t value)); 42 | static void toJson(JsonDocument& result, const State& state, const DeviceData& deviceData); 43 | static void doc2String(const JsonDocument& doc, String& result, bool pretty); 44 | 45 | static void channelDataToJsonArray(String& result, const String& msgId, const State& state); 46 | static bool channelDataFromJsonArray(const String& json, String& msgId, uint16_t result[16]); 47 | static void channelDataResponse( String& json, String& msgId); 48 | 49 | protected: 50 | static void parseChannelDataSection(const JsonObject& section, void (*cb_c)(uint8_t channel, uint16_t value)); 51 | }; -------------------------------------------------------------------------------- /src/util/logger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "logger.h" 19 | 20 | const char* Logger::level_str_[6] = {"F", "E", "W", "I", "D", "V"}; 21 | 22 | //------------------------------------------------------------------------------ 23 | Logger::Logger(const String& module) 24 | : Print(), 25 | module_(module) 26 | //------------------------------------------------------------------------------ 27 | {} 28 | 29 | //------------------------------------------------------------------------------ 30 | const void Logger::f(const char* msg, ...) 31 | //------------------------------------------------------------------------------ 32 | { 33 | print("\u001b[41m"); // background red 34 | printPrefix(FATAL); 35 | 36 | char buffer[1024]; 37 | va_list args; 38 | va_start(args, msg); 39 | vsprintf(buffer, msg, args); 40 | va_end(args); 41 | print(&buffer[0]); 42 | 43 | print("\u001b[0m"); // reset 44 | write('\n'); 45 | } 46 | 47 | //------------------------------------------------------------------------------ 48 | const void Logger::e(const char* msg, ...) 49 | //------------------------------------------------------------------------------ 50 | { 51 | print("\u001b[31;1m"); // bright red 52 | printPrefix(ERROR); 53 | 54 | char buffer[1024]; 55 | va_list args; 56 | va_start(args, msg); 57 | vsprintf(buffer, msg, args); 58 | va_end(args); 59 | print(&buffer[0]); 60 | 61 | print("\u001b[0m"); // reset 62 | write('\n'); 63 | } 64 | 65 | //------------------------------------------------------------------------------ 66 | const void Logger::w(const char* msg, ...) 67 | //------------------------------------------------------------------------------ 68 | { 69 | print("\u001b[33;1m"); // bright yellow 70 | printPrefix(WARN); 71 | 72 | char buffer[1024]; 73 | va_list args; 74 | va_start(args, msg); 75 | vsprintf(buffer, msg, args); 76 | va_end(args); 77 | print(&buffer[0]); 78 | 79 | print("\u001b[0m"); // reset 80 | write('\n'); 81 | } 82 | 83 | //------------------------------------------------------------------------------ 84 | const void Logger::i(const char* msg, ...) 85 | //------------------------------------------------------------------------------ 86 | { 87 | printPrefix(INFO); 88 | 89 | char buffer[1024]; 90 | va_list args; 91 | va_start(args, msg); 92 | vsprintf(buffer, msg, args); 93 | va_end(args); 94 | print(&buffer[0]); 95 | 96 | write('\n'); 97 | } 98 | 99 | //------------------------------------------------------------------------------ 100 | const void Logger::d(const char* msg, ...) 101 | //------------------------------------------------------------------------------ 102 | { 103 | printPrefix(DEBUG); 104 | 105 | char buffer[1024]; 106 | va_list args; 107 | va_start(args, msg); 108 | vsprintf(buffer, msg, args); 109 | va_end(args); 110 | print(&buffer[0]); 111 | 112 | write('\n'); 113 | } 114 | 115 | //------------------------------------------------------------------------------ 116 | const void Logger::v(const char* msg, ...) 117 | //------------------------------------------------------------------------------ 118 | { 119 | printPrefix(VERBOSE); 120 | 121 | char buffer[1024]; 122 | va_list args; 123 | va_start(args, msg); 124 | vsprintf(buffer, msg, args); 125 | va_end(args); 126 | print(&buffer[0]); 127 | 128 | write('\n'); 129 | } 130 | 131 | //------------------------------------------------------------------------------ 132 | size_t Logger::write(uint8_t c) 133 | //------------------------------------------------------------------------------ 134 | { 135 | return Serial.write(c); 136 | }; 137 | 138 | //------------------------------------------------------------------------------ 139 | const void Logger::printPrefix(Loglevel_t loglevel) 140 | //------------------------------------------------------------------------------ 141 | { 142 | using namespace std::chrono; 143 | auto ms = duration_cast(high_resolution_clock::now().time_since_epoch()); 144 | 145 | auto secs = duration_cast(ms); 146 | ms -= duration_cast(secs); 147 | auto mins = duration_cast(secs); 148 | secs -= duration_cast(mins); 149 | auto hour = duration_cast(mins); 150 | mins -= duration_cast(hour); 151 | 152 | Serial.print("* ["); 153 | Serial.print(level_str_[loglevel]); 154 | Serial.print("] ["); 155 | Serial.printf("%d:%02d:%02d.%03d", (int16_t)hour.count(), (int16_t)mins.count(), (int16_t)secs.count(), (int16_t)ms.count()); 156 | Serial.print("] ["); 157 | Serial.print(module_); 158 | Serial.print("] - "); 159 | } 160 | -------------------------------------------------------------------------------- /src/util/logger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #ifndef LOGGER_BUFFER_SIZE 25 | #define LOGGER_BUFFER_SIZE 1024 26 | #endif 27 | 28 | class Logger : public Print { 29 | public: 30 | enum Loglevel_t { FATAL, ERROR, WARN, INFO, DEBUG, VERBOSE }; 31 | 32 | Logger(const String& module); 33 | 34 | const void f(const char* msg, ...); 35 | const void e(const char* msg, ...); 36 | const void w(const char* msg, ...); 37 | const void i(const char* msg, ...); 38 | const void d(const char* msg, ...); 39 | const void v(const char* msg, ...); 40 | 41 | virtual size_t write(uint8_t c); 42 | 43 | private: 44 | const void printPrefix(Loglevel_t loglevel); 45 | 46 | const String module_; 47 | static const char* level_str_[6]; 48 | char buffer[LOGGER_BUFFER_SIZE]; 49 | }; -------------------------------------------------------------------------------- /src/util/multitimer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "multitimer.h" 19 | 20 | //------------------------------------------------------------------------------ 21 | MultiTimer::MultiTimer() 22 | //------------------------------------------------------------------------------ 23 | {} 24 | 25 | //------------------------------------------------------------------------------ 26 | bool MultiTimer::begin() 27 | //------------------------------------------------------------------------------ 28 | { 29 | return true; 30 | } 31 | 32 | //------------------------------------------------------------------------------ 33 | void MultiTimer::set(String name, uint64_t periodMs, TimerCallback cb) 34 | //------------------------------------------------------------------------------ 35 | { 36 | TimerData* temp = new TimerData(); 37 | temp->nextCall = esp_timer_get_time(); 38 | temp->period = periodMs * 1000; 39 | temp->cb = cb; 40 | timers_[name] = temp; 41 | } 42 | 43 | //------------------------------------------------------------------------------ 44 | void MultiTimer::remove(String name) 45 | //------------------------------------------------------------------------------ 46 | { 47 | std::map::const_iterator it = timers_.find(name); 48 | if (it != timers_.end()) { 49 | if (it->second) { 50 | delete it->second; 51 | } 52 | } 53 | timers_.erase(name); 54 | } 55 | 56 | //------------------------------------------------------------------------------ 57 | void MultiTimer::loop() 58 | //------------------------------------------------------------------------------ 59 | { 60 | uint64_t now = esp_timer_get_time(); 61 | std::for_each(timers_.begin(), timers_.end(), [&now](std::pair timer) { 62 | if (timer.second->nextCall <= now) { 63 | timer.second->nextCall += timer.second->period; 64 | timer.second->cb(); 65 | } 66 | }); 67 | } -------------------------------------------------------------------------------- /src/util/multitimer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | 23 | class MultiTimer { 24 | public: 25 | typedef void (*TimerCallback)(); 26 | 27 | MultiTimer(); 28 | 29 | bool begin(); 30 | void loop(); 31 | void set(String name, uint64_t periodMs, TimerCallback cb); 32 | void remove(String name); 33 | 34 | private: 35 | struct TimerData { 36 | uint64_t nextCall; 37 | uint64_t period; 38 | TimerCallback cb; 39 | uint64_t called; 40 | }; 41 | 42 | std::map timers_; 43 | }; -------------------------------------------------------------------------------- /src/util/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ESP32-LED-Dimmer distribution (https://github.com/zebrajaeger/esp32-led-dimmer). 3 | * Copyright (c) 2019 Lars Brandt. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include 23 | 24 | class Utils { 25 | public: 26 | static String createId() { 27 | char id[20]; 28 | uint8_t mac[6]; 29 | WiFi.macAddress(mac); 30 | sprintf(id, "esp32-%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 31 | return id; 32 | } 33 | }; -------------------------------------------------------------------------------- /src/webapp/index.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/esp32-led-dimmer/eb6a57fd1e1283afc3fe6c50c7b6f657c3d67a9e/src/webapp/index.html.gz -------------------------------------------------------------------------------- /test-artnet-sender/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /test-artnet-sender/README.md: -------------------------------------------------------------------------------- 1 | # Test Artnet Sender 2 | 3 | ## Install 4 | 5 | * Requires [NodeJs](https://nodejs.org/) 6 | * Run in this directory 7 | 8 | npm install 9 | 10 | ## Execute 11 | 12 | * Open 13 | 14 | index.js 15 | and change the host to the IP or hostname of your node 16 | * Run 17 | 18 | npm start 19 | -------------------------------------------------------------------------------- /test-artnet-sender/index.artnet.js: -------------------------------------------------------------------------------- 1 | const artnet = require('artnet')({ 2 | host: '192.168.178.34' 3 | }); 4 | 5 | function send() { 6 | let data = new Array(16); 7 | data.fill(0); 8 | for(let i=0; i<16;++i){ 9 | 10 | data[i] = Math.random() * 256; 11 | // data[i] = i*16; 12 | } 13 | console.log("SEND", data); 14 | artnet.set(1, 0, data); 15 | } 16 | 17 | setInterval(send, 50) 18 | -------------------------------------------------------------------------------- /test-artnet-sender/index.e131.js: -------------------------------------------------------------------------------- 1 | var e131 = require('e131'); 2 | 3 | const l = 30; // RGB Mode 4 | // const l = 10; // white mode 5 | // var client = new e131.Client('192.168.178.23'); 6 | var client = new e131.Client('192.168.178.34'); 7 | var packet = client.createPacket(l); // we want 8 RGB (x3) slots 8 | var slotsData = packet.getSlotsData(); 9 | packet.setSourceName('test E1.31 client'); 10 | packet.setUniverse(0x01); // make universe number consistent with the client 11 | packet.setOption(packet.Options.PREVIEW, true); // don't really change any fixture 12 | packet.setPriority(packet.DEFAULT_PRIORITY); // not strictly needed, done automatically 13 | 14 | setRGB(0, 255, 0, 0); 15 | setRGB(1, 0, 255, 0); 16 | setRGB(2, 0, 0, 255); 17 | setRGB(5, 255, 255, 255); 18 | //cycleColor() 19 | staticColor() 20 | 21 | function setRGB(index, r, g, b) { 22 | const i = index * 3; 23 | slotsData[i] = r; 24 | slotsData[i + 1] = g; 25 | slotsData[i + 2] = b; 26 | } 27 | 28 | function staticColor() { 29 | client.send(packet, function () { 30 | setTimeout(staticColor, 500); 31 | }); 32 | } 33 | 34 | function cycleColor() { 35 | let first = slotsData[0]; 36 | for (let i = 1; i < l; ++i) { 37 | slotsData[i - 1] = slotsData[i]; 38 | } 39 | slotsData[l - 1] = first; 40 | client.send(packet, function () { 41 | setTimeout(cycleColor, 50); 42 | }); 43 | } -------------------------------------------------------------------------------- /test-artnet-sender/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-artnet-sender", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.e131.js", 6 | "scripts": { 7 | "start": "nodemon index.e131.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "artnet": "1.4.0", 13 | "broadcast-address": "1.0.2", 14 | "e131": "^1.1.3", 15 | "nodemon": "^2.0.20" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test-artnet-sender/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1: 6 | version "1.1.1" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 8 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 9 | 10 | anymatch@~3.1.2: 11 | version "3.1.3" 12 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" 13 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 14 | dependencies: 15 | normalize-path "^3.0.0" 16 | picomatch "^2.0.4" 17 | 18 | artnet@1.4.0: 19 | version "1.4.0" 20 | resolved "https://registry.yarnpkg.com/artnet/-/artnet-1.4.0.tgz#8f00d8965acdc330486ce39fcc123c79e2ff7c45" 21 | integrity sha1-jwDYllrNwzBIbOOfzBI8eeL/fEU= 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.2" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 26 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 27 | 28 | binary-extensions@^2.0.0: 29 | version "2.2.0" 30 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 31 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 32 | 33 | brace-expansion@^1.1.7: 34 | version "1.1.11" 35 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 36 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 37 | dependencies: 38 | balanced-match "^1.0.0" 39 | concat-map "0.0.1" 40 | 41 | braces@~3.0.2: 42 | version "3.0.2" 43 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 44 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 45 | dependencies: 46 | fill-range "^7.0.1" 47 | 48 | broadcast-address@1.0.2: 49 | version "1.0.2" 50 | resolved "https://registry.yarnpkg.com/broadcast-address/-/broadcast-address-1.0.2.tgz#a8c6a70561140a51433a3a6549698215e0777e38" 51 | integrity sha512-G9bslPECDbowXbH8+08zqBha14LtRofoTT6DUQxePXyv3AaU0It+vkhPq+tENJB0MNrC5fxPqpM7Fqk+vME/hA== 52 | dependencies: 53 | has-deep-value "1.1.0" 54 | 55 | chokidar@^3.5.2: 56 | version "3.5.3" 57 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 58 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 59 | dependencies: 60 | anymatch "~3.1.2" 61 | braces "~3.0.2" 62 | glob-parent "~5.1.2" 63 | is-binary-path "~2.1.0" 64 | is-glob "~4.0.1" 65 | normalize-path "~3.0.0" 66 | readdirp "~3.6.0" 67 | optionalDependencies: 68 | fsevents "~2.3.2" 69 | 70 | concat-map@0.0.1: 71 | version "0.0.1" 72 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 73 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 74 | 75 | debug@^3.2.7: 76 | version "3.2.7" 77 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 78 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== 79 | dependencies: 80 | ms "^2.1.1" 81 | 82 | e131@^1.1.3: 83 | version "1.1.3" 84 | resolved "https://registry.yarnpkg.com/e131/-/e131-1.1.3.tgz#1d13d479f2eebdf0ad5c158121f0b7095bb490b5" 85 | integrity sha512-qku740afUkGubZlHMrLoljbcMn4wGBFGq8jL9z/j1Gx+aTWytit7Rb5u7HtzJ/84fw0mAcv0YPCDB4FIG/bdjw== 86 | 87 | fill-range@^7.0.1: 88 | version "7.0.1" 89 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 90 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 91 | dependencies: 92 | to-regex-range "^5.0.1" 93 | 94 | fsevents@~2.3.2: 95 | version "2.3.2" 96 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 97 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 98 | 99 | glob-parent@~5.1.2: 100 | version "5.1.2" 101 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 102 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 103 | dependencies: 104 | is-glob "^4.0.1" 105 | 106 | has-deep-value@1.1.0: 107 | version "1.1.0" 108 | resolved "https://registry.yarnpkg.com/has-deep-value/-/has-deep-value-1.1.0.tgz#1982cdacbba4d345985ab00c5b23171626700129" 109 | integrity sha512-vK01gd6Pkq/9aOZucinrI03+jt85gzkdEd3DHlOWcV25xEo222BkR5utJcbqJizJaLXVqUP42brXp98pm6jFNQ== 110 | 111 | has-flag@^3.0.0: 112 | version "3.0.0" 113 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 114 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 115 | 116 | ignore-by-default@^1.0.1: 117 | version "1.0.1" 118 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 119 | integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== 120 | 121 | is-binary-path@~2.1.0: 122 | version "2.1.0" 123 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 124 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 125 | dependencies: 126 | binary-extensions "^2.0.0" 127 | 128 | is-extglob@^2.1.1: 129 | version "2.1.1" 130 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 131 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 132 | 133 | is-glob@^4.0.1, is-glob@~4.0.1: 134 | version "4.0.3" 135 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 136 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 137 | dependencies: 138 | is-extglob "^2.1.1" 139 | 140 | is-number@^7.0.0: 141 | version "7.0.0" 142 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 143 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 144 | 145 | minimatch@^3.1.2: 146 | version "3.1.2" 147 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 148 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 149 | dependencies: 150 | brace-expansion "^1.1.7" 151 | 152 | ms@^2.1.1: 153 | version "2.1.3" 154 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 155 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 156 | 157 | nodemon@^2.0.20: 158 | version "2.0.20" 159 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" 160 | integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw== 161 | dependencies: 162 | chokidar "^3.5.2" 163 | debug "^3.2.7" 164 | ignore-by-default "^1.0.1" 165 | minimatch "^3.1.2" 166 | pstree.remy "^1.1.8" 167 | semver "^5.7.1" 168 | simple-update-notifier "^1.0.7" 169 | supports-color "^5.5.0" 170 | touch "^3.1.0" 171 | undefsafe "^2.0.5" 172 | 173 | nopt@~1.0.10: 174 | version "1.0.10" 175 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 176 | integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== 177 | dependencies: 178 | abbrev "1" 179 | 180 | normalize-path@^3.0.0, normalize-path@~3.0.0: 181 | version "3.0.0" 182 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 183 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 184 | 185 | picomatch@^2.0.4, picomatch@^2.2.1: 186 | version "2.3.1" 187 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 188 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 189 | 190 | pstree.remy@^1.1.8: 191 | version "1.1.8" 192 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" 193 | integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== 194 | 195 | readdirp@~3.6.0: 196 | version "3.6.0" 197 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 198 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 199 | dependencies: 200 | picomatch "^2.2.1" 201 | 202 | semver@^5.7.1: 203 | version "5.7.1" 204 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 205 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 206 | 207 | semver@~7.0.0: 208 | version "7.0.0" 209 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" 210 | integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== 211 | 212 | simple-update-notifier@^1.0.7: 213 | version "1.1.0" 214 | resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" 215 | integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== 216 | dependencies: 217 | semver "~7.0.0" 218 | 219 | supports-color@^5.5.0: 220 | version "5.5.0" 221 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 222 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 223 | dependencies: 224 | has-flag "^3.0.0" 225 | 226 | to-regex-range@^5.0.1: 227 | version "5.0.1" 228 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 229 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 230 | dependencies: 231 | is-number "^7.0.0" 232 | 233 | touch@^3.1.0: 234 | version "3.1.0" 235 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 236 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== 237 | dependencies: 238 | nopt "~1.0.10" 239 | 240 | undefsafe@^2.0.5: 241 | version "2.0.5" 242 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" 243 | integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== 244 | -------------------------------------------------------------------------------- /test-ws-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 34 | 70 | 71 | 72 |
73 |
url

74 |
Connectionstate
75 |
76 |
No Data received
77 |
78 | 79 |
80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /test-ws-server/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /test-ws-server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 36 | 72 | 73 | 74 |
75 |
Connectionstate
76 |
77 |
No Data received
78 |
79 | 80 |
81 |
82 | 83 | 84 | -------------------------------------------------------------------------------- /test-ws-server/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const express = require('express')(); 5 | const WebSocket = require('ws'); 6 | 7 | const open = require('open'); 8 | 9 | let channels = new Array(16); 10 | channels.fill(1024); 11 | 12 | // http-server 13 | express.get('/', function (req, res) { 14 | res.sendFile(path.join(__dirname, 'index.html')); 15 | }); 16 | express.listen(80); 17 | 18 | 19 | // WS Server 20 | const wss = new WebSocket.Server({port: 81}); 21 | wss.on('connection', ws => { 22 | console.log('WS connected'); 23 | 24 | let timerHandler = setInterval(() => { 25 | console.log("WS SEND"); 26 | ws.send(JSON.stringify(channels)); 27 | }, 2000); 28 | 29 | ws.on('message', message => { 30 | console.log('WS received: %s', message); 31 | ws.send(JSON.stringify(channels)); 32 | }); 33 | 34 | ws.on('close', function close() { 35 | console.log('WS disconnected'); 36 | clearInterval(timerHandler); 37 | }); 38 | 39 | }); 40 | 41 | console.log("STARTED"); 42 | open('http://localhost:80'); 43 | -------------------------------------------------------------------------------- /test-ws-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-ws-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "s": "node index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "open": "^7.0.0", 14 | "ws": "^7.2.1" 15 | } 16 | } 17 | --------------------------------------------------------------------------------