├── LICENSE ├── README.md ├── _config.yml ├── assets ├── Clock Message Board Case.stl └── static_view.jpg ├── clock-message-board.ino ├── configuration.yaml ├── example_config.h ├── images └── clock-message-board.jpg └── sensors.yaml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ptrsnja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [ View or clone the project repository on GitHub](https://github.com/jptrsn/clock-message-board "View the project on GitHub") 4 | 5 | An IoT clock that will also display messages posted to an MQTT topic. Uses an ESP8266, MAX7219 LED display matrix modules, and the MD_MAX72xx and MD_PAROLA libraries, as well as WS2812B LED strip. 6 | 7 | All code and related files are copyright 2018, James Petersen, published under an MIT license. See [https://github.com/jptrsn/clock-message-board/blob/master/LICENSE](https://github.com/jptrsn/clock-message-board/blob/master/LICENSE) for information about use, modification, and sharing restrictions. Generally, unless you want to make money from this project, you are free to use, modify or share anything found within. The project was originally designed to integrate well with [Home Assistant](http://www.homeassistant.io) and [Node Red](https://nodered.org/), but should work with any MQTT broker that supports JSON objects. If you do not have an MQTT broker configured, you may want to look at [CloudMQTT](https://www.cloudmqtt.com/) or [Adafruit IO](https://io.adafruit.com/) to get started without any additional hardware. 8 | 9 | ## Features 10 | - Automatic time synchronization with NTP servers. 11 | - Includes readings from HTU21D (SI7201) Temperature/Humidity sensor (attached through I2C) 12 | - Over-The-Air Updates (OTA) *(board must have sufficient flash memory; tested on a WeMos D1 Mini)* 13 | - Subscribe to an MQTT topic to display other relevant information using a JSON object (optional, it is not required) 14 | - Auto-reconnect to WiFi and/or MQTT 15 | - Status LEDs around frame using individually-addressable WS2812B strip 16 | 17 | ## Dependencies 18 | Some basic libraries are required, which you can install through the library manager. 19 | - Wire.h 20 | - ESP8266WiFi.h 21 | - SPI.h 22 | - WiFiUdp.h 23 | 24 | In addition to the above libraries, the code depends on some third-party libraries developed by these very nice people: 25 | - MD_MAX72XX library by majicDesigns: [https://majicdesigns.github.io/MD_MAX72XX/] 26 | - MD_Parola library by majicDesigns: [https://majicdesigns.github.io/MD_Parola/index.html] 27 | - HTU21D library from enjoyneering: [https://github.com/enjoyneering/HTU21D] 28 | - ~~SparkFun HTU21D library: [https://learn.sparkfun.com/tutorials/htu21d-humidity-sensor-hookup-guide/htu21d-library-and-functions]~~ *(deprecated as of v0.2)* 29 | - ~~NTP Client library by Fabrice Weinberg: [https://github.com/arduino-libraries/NTPClient]~~ *(deprecated as of v0.2)* 30 | - TimeLib library by Paul Stoffregen [https://github.com/PaulStoffregen/Time] 31 | - Timezone library by Jack Christensen [https://github.com/JChristensen/Timezone] 32 | - PubSub Client library by Nick O'Leary: [https://pubsubclient.knolleary.net/] 33 | - ArduinoJson library by Benoit Blanchon: [https://bblanchon.github.io/ArduinoJson/] 34 | - ArduinoOTA library by Ivan Grokhotkov and Miguel Angel Ajo [https://github.com/esp8266/Arduino/tree/master/libraries/ArduinoOTA] 35 | - FastLED library for WS2812Bs: [http://fastled.io/] 36 | 37 | ## Setting it up 38 | A sample configuration file has been included. This allows you to set default properties. As provided, the code will run on a **Wemos D1 Mini board** but it should be 39 | compatible with any ESP8266 module that supports SPI. If you are using a different board, you may need to change the default values. 40 | 41 | **By default, the sketch asks to include `config.h`, which is not included in this repository.** You may want to either create a new `config.h` file, or change the name of `example_config.h`. 42 | 43 | ` #define DEBUG 0` 44 | Allows you to turn on/off debugging output to serial monitor. Useful for developing over USB, but not useful when running in stand-alone mode. Potentially causes conflicts with SPI or WS2812 communication (depending on your configuration) - recommended to set to 0 unless actively developing the software. 45 | 46 | ## The Message object 47 | The MQTT topic should contain a JSON object with a "message" property. The full structure of the JSON object is: 48 | ``` 49 | { 50 | "message": {{String}}, [required] 51 | "frameDelay": {{Integer}}, [optional] 52 | "repeat": {{Integer}}, [optional] 53 | "textEffect": { 54 | "in": {{Integer}}, [optional] 55 | "out": {{Integer}}, [optional] 56 | "pause": {{Integer}} [optional] 57 | } 58 | } 59 | ``` 60 | 61 | Additional parameters are available that allow you to customize a single message. Text effects, repeat, frame rate, and pause will be set for the message, then restored to defaults after the message is no longer displayed. To change defaults, use the `config.h` file. 62 | 63 | ### Customize a message 64 | If you wish to change the way a message is rendered, add any of the optional properties to the JSON object that is published to your MQTT message topic. 65 | 66 | #### frameDelay _(Integer)_ 67 | The number of milliseconds between each animation frame. This is useful if you want to slow down scrolling text. 68 | 69 | #### repeat _(Integer)_ 70 | The number of times to repeat a given message. Can be useful, since animation will draw your eye to the clock, but generally not before part of the message has animated off screen (if it's a long message). 71 | 72 | #### textEffect _(JSON object)_ 73 | 74 | ##### textEffect.in _(Integer)_ 75 | The animation to use to transition the display from blank to showing the message. 76 | 77 | ##### textEffect.out _(Integer)_ 78 | The animation to use to transition the display from showing the message to a blank state. 79 | 80 | ##### textEffect.pause _(Integer)_ 81 | The number of milliseconds to wait while the display is showing the message. For scrolling messages, this is not required. Generally, long messages 82 | should not define a pause, while shorter messages that can fit on the entire display at one time can be paused. 83 | 84 | #### Text Effect Array 85 | The text effect arguments are the index of the text effect defined in the array `effects`. See the list below for a quick reference, or look at the sketch code. Please note that if you modify this array, the resultant behaviour may not match your specified effects. 86 | 87 | ``` 88 | PA_PRINT, // 0 89 | PA_SCAN_HORIZ, // 1 90 | PA_SCROLL_LEFT, // 2 91 | PA_WIPE, // 3 92 | PA_SCROLL_UP_LEFT, // 4 93 | PA_SCROLL_UP, // 5 94 | PA_FADE, // 6 95 | PA_OPENING_CURSOR, // 7 96 | PA_GROW_UP, // 8 97 | PA_SCROLL_UP_RIGHT, // 9 98 | PA_BLINDS, // 10 99 | PA_CLOSING, // 11 100 | PA_GROW_DOWN, // 12 101 | PA_SCAN_VERT, // 13 102 | PA_SCROLL_DOWN_LEFT, // 14 103 | PA_WIPE_CURSOR, // 15 104 | PA_DISSOLVE, // 16 105 | PA_MESH, // 17 106 | PA_OPENING, // 18 107 | PA_CLOSING_CURSOR, // 19 108 | PA_SCROLL_DOWN_RIGHT, // 20 109 | PA_SCROLL_RIGHT, // 21 110 | PA_SLICE, // 22 111 | PA_SCROLL_DOWN, // 23 112 | ``` 113 | 114 | ## OTA updates 115 | The display will let you know when it begins the software update, display an animation as the update is applied, confirm the success of the update, then reset itself. This should allow zero-touch updates to be pushed to the device. 116 | 117 | ## WS2812B LED strip 118 | There is an option to include a WS2812B strip of LEDs, designed to encompass the face of the message board. The code is an almost exact duplicate of the amazing [Bruh Automation](http://www.bruhautomation.com) and specifically his MQTT JSON lights project. [https://github.com/bruhautomation/ESP-MQTT-JSON-Digital-LEDs](https://github.com/bruhautomation/ESP-MQTT-JSON-Digital-LEDs). 119 | 120 | The WS2812B LEDs attached to `WS_PIN` will behave the same as any other MQTT JSON light. You can follow the tutorial videos in the repository linked above to get a better understanding of how to use the light component in Home Assistant. 121 | 122 | **Please note** that using a basic ESP8266 module with WS2812B LEDs can be difficult, and some pins will not work properly due to interrupts. An ESP07 has been tested, and GPIO 03 is confirmed to work, while GPIO 04, GPIO 05 and GPIO 12 are all confirmed to not work. If you are having difficulty getting the WS2812s to work, please ensure that you are using a supported pin. The software author will not provide any support for problems with the WS2812 LEDs. 123 | 124 | ## 3D Printed Case ## 125 | A sample 3D printed case is included in the repository. It was created on [Autodesk Tinkercad](http://www.tinkercad.com), and can be printed directly from the included .stl file. Alternatively, you can [get the source file directly](https://www.tinkercad.com/things/1tm8isQnekp-clock-message-board-case) in order to remix or modify the case in any way. The 3D model is covered by a [Creative Commons CC-BY-NC-SA 3.0 License](https://creativecommons.org/licenses/by-nc-sa/3.0/legalcode) 126 | 127 | ![CC-BY-NC-SA 3.0](https://i.creativecommons.org/l/by-nc/4.0/88x31.png) 128 | 129 | ## Roadmap/Wishlist 130 | - Schematic and PCB design for other ESP modules (ESP-07, ESP-12, or ESP-32) 131 | - ~~Add support for RGB LED strips~~ (Added January 2018) 132 | - Special character handling in message string 133 | - Zones for displaying multiple metrics concurrently 134 | - Use WiFi Manager library for handling WiFi credentials and connections 135 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-tactile -------------------------------------------------------------------------------- /assets/Clock Message Board Case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jptrsn/clock-message-board/6050d5f50ff4d304ad741d5f2df0d6bd8843301d/assets/Clock Message Board Case.stl -------------------------------------------------------------------------------- /assets/static_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jptrsn/clock-message-board/6050d5f50ff4d304ad741d5f2df0d6bd8843301d/assets/static_view.jpg -------------------------------------------------------------------------------- /clock-message-board.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | MQTT Clock & Message Board v 0.2.0 4 | Project by James Petersen, copyright 2018. Source code and documentation available at https://github.com/jptrsn/clock-message-board 5 | 6 | Code has been tested on a Wemos D1 Mini and a bare ESP-07 module. 7 | 8 | Source code and documentation are published under a Creative Commons Attribution-ShareAlike 4.0 License 9 | https://creativecommons.org/licenses/by-nc/4.0/ 10 | 11 | */ 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | // https://github.com/FastLED/FastLED/wiki/Interrupt-problems 25 | //#define FASTLED_ALLOW_INTERRUPTS 0 26 | #define FASTLED_INTERRUPT_RETRY_COUNT 1 27 | #include 28 | 29 | #include "config.h" 30 | 31 | // HARDWARE SPI 32 | MD_Parola P = MD_Parola(MD_MAX72XX::FC16_HW, CS_PIN, MAX_DEVICES); 33 | 34 | // WiFi login parameters - network name and password 35 | const char* ssid = CONFIG_WIFI_SSID; 36 | const char* password = CONFIG_WIFI_PASS; 37 | 38 | const char* mqtt_server = CONFIG_MQTT_HOST; 39 | const char* mqtt_username = CONFIG_MQTT_USER; 40 | const char* mqtt_password = CONFIG_MQTT_PASS; 41 | const char* client_id = CONFIG_MQTT_CLIENT_ID; 42 | const char* host_name = CONFIG_HOST_NAME; 43 | const int BUFFER_SIZE = JSON_OBJECT_SIZE(10); 44 | 45 | const char* message_topic = CONFIG_MQTT_TOPIC_MESSAGE; 46 | const char* command_topic = CONFIG_MQTT_TOPIC_COMMAND; 47 | const char* light_state_topic = CONFIG_MQTT_LIGHT_STATE; 48 | const char* light_set_topic = CONFIG_MQTT_LIGHT_SET; 49 | 50 | const char* on_cmd = CONFIG_ON_CMD; 51 | const char* off_cmd = CONFIG_OFF_CMD; 52 | const char* stripEffect = "solid"; 53 | String stripEffectString = "solid"; 54 | String oldstripEffectString = "solid"; 55 | bool revertToOldStripEffect = false; 56 | bool revertToOff = false; 57 | 58 | /*********************************** FastLED Defintions ********************************/ 59 | byte realRed = 0; 60 | byte realGreen = 0; 61 | byte realBlue = 0; 62 | 63 | byte stripRed = 255; 64 | byte stripGreen = 255; 65 | byte stripBlue = 255; 66 | byte stripBrightness = 255; 67 | 68 | /******************************** GLOBALS for fade/flash *******************************/ 69 | bool stateOn = false; 70 | bool startFade = false; 71 | bool onbeforeflash = false; 72 | unsigned long lastLoop = 0; 73 | int transitionTime = 0; 74 | int stripEffectSpeed = 0; 75 | bool inFade = false; 76 | int loopCount = 0; 77 | int stepR, stepG, stepB; 78 | int rdVal, grnVal, bluVal; 79 | 80 | bool flash = false; 81 | bool startFlash = false; 82 | int flashLength = 0; 83 | unsigned long flashStartTime = 0; 84 | byte flashRed = stripRed; 85 | byte flashGreen = stripGreen; 86 | byte flashBlue = stripBlue; 87 | byte flashBrightness = stripBrightness; 88 | 89 | WiFiClient espClient; 90 | PubSubClient client(espClient); 91 | 92 | HTU21D sensor(HTU21D_RES_RH12_TEMP14); 93 | //SHT21 sensor; 94 | float publishedHumidity = 0; 95 | float publishedTemperature = 0; 96 | 97 | WiFiUDP Udp; 98 | unsigned int localPort = 8888; // local port to listen for UDP packets 99 | time_t getNtpTime(); 100 | void sendNTPpacket(IPAddress &address); 101 | TimeChangeRule myDST = {"EDT", Second, Sun, Mar, 2, -240}; //Daylight time = UTC - 4 hours 102 | TimeChangeRule mySTD = {"EST", First, Sun, Nov, 2, -300}; //Standard time = UTC - 5 hours 103 | Timezone myTZ(myDST, mySTD); 104 | TimeChangeRule *tcr; //pointer to the time change rule, use to get TZ abbrev 105 | time_t local; 106 | 107 | uint8_t frameDelay = FRAME_DELAY_DEFAULT; // default frame delay value 108 | textEffect_t effects[] = 109 | { 110 | PA_PRINT, // 0 111 | PA_SCAN_HORIZ, // 1 112 | PA_SCROLL_LEFT, // 2 113 | PA_WIPE, // 3 114 | PA_SCROLL_UP_LEFT, // 4 115 | PA_SCROLL_UP, // 5 116 | PA_FADE, // 6 117 | PA_OPENING_CURSOR, // 7 118 | PA_GROW_UP, // 8 119 | PA_SCROLL_UP_RIGHT, // 9 120 | PA_BLINDS, // 10 121 | PA_CLOSING, // 11 122 | PA_GROW_DOWN, // 12 123 | PA_SCAN_VERT, // 13 124 | PA_SCROLL_DOWN_LEFT, // 14 125 | PA_WIPE_CURSOR, // 15 126 | PA_DISSOLVE, // 16 127 | PA_MESH, // 17 128 | PA_OPENING, // 18 129 | PA_CLOSING_CURSOR, // 19 130 | PA_SCROLL_DOWN_RIGHT, // 20 131 | PA_SCROLL_RIGHT, // 21 132 | PA_SLICE, // 22 133 | PA_SCROLL_DOWN, // 23 134 | }; 135 | 136 | // Global message buffers shared by Wifi and Scrolling functions 137 | #define BUF_SIZE CONFIG_BUF_SIZE 138 | char curMessage[BUF_SIZE]; 139 | char newMessage[BUF_SIZE]; 140 | bool newMessageAvailable = false; 141 | char currentTime[BUF_SIZE]; 142 | 143 | 144 | // Global variables that need to be available to code 145 | 146 | textEffect_t scrollEffect = CONFIG_DEFAULT_EFFECT; 147 | textEffect_t effectIn = effects[2]; 148 | textEffect_t effectOut = effects[2]; 149 | uint16_t messagePause = 0; 150 | 151 | unsigned long lastSensorUpdate; 152 | unsigned long lastMessageDisplayed; 153 | bool updateLastMessageDisplayed; 154 | 155 | byte lastMinute; 156 | byte machineState = 1; 157 | byte repeatMessage = MESSAGE_REPEAT_DEFAULT; 158 | byte i = 0; 159 | byte clockBrightness = CLOCK_BRIGHTNESS_DEFAULT; 160 | byte messageBrightness = MESSAGE_BRIGHTNESS_DEFAULT; 161 | int delayBetweenMessages = MESSAGE_DELAY_DEFAULT; 162 | 163 | void setup() { 164 | delay(500); 165 | 166 | pinMode(LED_PIN, OUTPUT); 167 | 168 | lastSensorUpdate = millis(); 169 | lastMessageDisplayed = millis(); 170 | if (DEBUG) { 171 | Serial.begin(115200); 172 | } 173 | 174 | configureLedStrip(); 175 | 176 | P.begin(); 177 | P.displayClear(); 178 | P.setIntensity(clockBrightness); 179 | P.setTextEffect(PA_SCROLL_UP, PA_SCROLL_DOWN); 180 | 181 | P.displayScroll(curMessage, PA_CENTER, scrollEffect, FRAME_DELAY_DEFAULT); 182 | P.displayReset(); 183 | curMessage[0] = newMessage[0] = '\0'; 184 | 185 | // Connect to and initialise WiFi network 186 | PRINT("\nConnecting to ", ssid); 187 | sprintf(curMessage, "%s %s", "Connecting to ", ssid); 188 | 189 | WiFi.begin(ssid, password); 190 | WiFi.hostname(host_name); 191 | waitForMessageComplete(true); 192 | 193 | handleWifi(false); 194 | 195 | configureOTA(); 196 | 197 | Udp.begin(localPort); 198 | setSyncProvider(getNtpTime); 199 | setSyncInterval(120); 200 | 201 | client.setServer(mqtt_server, 1883); 202 | client.setCallback(callback); 203 | 204 | sensor.begin(SDA, SCL); 205 | 206 | PRINTS("\nWiFi connected"); 207 | getNtpTime(); 208 | updateCurrentTime(); 209 | 210 | P.displayReset(); 211 | sprintf(curMessage, "%d.%d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); 212 | PRINT("\nAssigned IP ", curMessage); 213 | waitForMessageComplete(false); 214 | 215 | digitalWrite(LED_PIN, HIGH); 216 | } 217 | 218 | void loop() { 219 | 220 | ArduinoOTA.handle(); 221 | 222 | if (client.connected()) { 223 | client.loop(); 224 | } 225 | 226 | handleLedStrip(); 227 | 228 | if (P.displayAnimate()) { 229 | 230 | // Ensure WiFi is connected. Enable error output to the MAX7219 display. 231 | handleWifi(true); 232 | 233 | // If we're not connected to the MQTT message topic, attempt to reconnect 234 | if (!client.connected()) { 235 | reconnect(); 236 | } else { 237 | client.loop(); 238 | } 239 | 240 | if (revertToOldStripEffect) { 241 | stripEffectString = oldstripEffectString; 242 | if (revertToOff) { 243 | stateOn = false; 244 | revertToOff = false; 245 | } 246 | revertToOldStripEffect = false; 247 | } 248 | 249 | // If the flag is set to update the timestamp of the last message display, do so. If there's a new message 250 | // to display, do not reset the flag 251 | if (updateLastMessageDisplayed && !newMessageAvailable) { 252 | lastMessageDisplayed = millis(); 253 | updateLastMessageDisplayed = false; 254 | } 255 | 256 | // Run state machine evaluations 257 | handlemachineState(); 258 | } 259 | } 260 | 261 | /***********************************************************************************/ 262 | // State Machine methods 263 | 264 | void handlemachineState() { 265 | // Write the machine state to the serial monitor if it is not in static-display mode (state 0) 266 | if (DEBUG && machineState) { 267 | PRINTS("machineState: "); 268 | PRINTLN(machineState); 269 | } 270 | 271 | switch (machineState) { 272 | case 0: { // Clock is showing the current time 273 | if (i > 0 && millis() - lastMessageDisplayed > delayBetweenMessages && !newMessageAvailable) { 274 | PRINTLN("---"); 275 | PRINT("---------------- Message repeat ", i); 276 | PRINTLN(" ----------------"); 277 | machineState = 3; 278 | } else if (newMessageAvailable && machineState != 1) { 279 | strcpy(curMessage, newMessage); 280 | machineState = 3; 281 | } else { 282 | if (clockMinuteChanged(lastMinute)) { 283 | machineState = 5; 284 | } else if (USE_SENSOR) { 285 | readSensor(); 286 | } 287 | } 288 | break; 289 | } 290 | case 1: { // Clock is transitioning from hidden to showing current time 291 | showTime(); 292 | machineState = 0; 293 | break; 294 | } 295 | case 2: { 296 | 297 | break; 298 | } 299 | case 3: { // Clock is transitioning from showing current time to hidden, in order to display a new message 300 | PRINT("repeatMessage: ", repeatMessage); 301 | PRINTLN(" "); 302 | if (i < repeatMessage) { 303 | hideTime(); 304 | newMessageAvailable = false; 305 | machineState = 4; 306 | } else { 307 | repeatMessage = MESSAGE_REPEAT_DEFAULT; 308 | frameDelay = FRAME_DELAY_DEFAULT; 309 | messagePause = MESSAGE_PAUSE_DEFAULT; 310 | P.setSpeed(frameDelay); 311 | i = 0; 312 | machineState = 0; 313 | PRINTLN("Do not display"); 314 | } 315 | break; 316 | } 317 | case 4: { // New message received, needs to be animated 318 | PRINTS("display message - "); 319 | PRINTLN(curMessage); 320 | // P.displayScroll(curMessage, PA_CENTER, PA_SCROLL_LEFT, frameDelay); 321 | P.displayText(curMessage, PA_CENTER, frameDelay, messagePause, effectIn, effectOut); 322 | i++; 323 | updateLastMessageDisplayed = true; 324 | machineState = 6; 325 | break; 326 | } 327 | case 5: { // Clock is displaying exit animation 328 | hideTime(); 329 | machineState = 6; 330 | break; 331 | } 332 | case 6: { // Clock display is blank, value can be changed 333 | updateCurrentTime(); 334 | machineState = 1; 335 | break; 336 | } 337 | } 338 | } 339 | 340 | /***********************************************************************************/ 341 | // Clock methods 342 | 343 | bool clockMinuteChanged(byte& thisMinute) { 344 | if (minute() != thisMinute) { 345 | // PRINTLN("Time changed"); 346 | thisMinute = minute(); 347 | return true; 348 | } 349 | return false; 350 | } 351 | 352 | void updateCurrentTime() { 353 | // PRINTLN("updateCurrentTime"); 354 | local = myTZ.toLocal(now(), &tcr); 355 | sprintf(currentTime, "%d:%02d", hour(local), minute(local)); 356 | PRINTLN(currentTime); 357 | // getNtpTime(); 358 | } 359 | 360 | // Animate the current time off the display 361 | void hideTime() { 362 | P.displayText(currentTime, PA_CENTER, FRAME_DELAY_DEFAULT * 2, 0, PA_PRINT, PA_SCROLL_UP); 363 | } 364 | 365 | // Animate the current time onto the display 366 | void showTime() { 367 | // PRINTLN("showTime"); 368 | P.displayText(currentTime, PA_CENTER, FRAME_DELAY_DEFAULT * 2, 500, PA_SCROLL_UP); 369 | } 370 | 371 | /*-------- NTP code ----------*/ 372 | 373 | const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message 374 | byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets 375 | 376 | time_t getNtpTime() 377 | { 378 | IPAddress ntpServerIP; // NTP server's ip address 379 | 380 | while (Udp.parsePacket() > 0) ; // discard any previously received packets 381 | // get a random server from the pool 382 | WiFi.hostByName(NTP_SERVER_POOL, ntpServerIP); 383 | sendNTPpacket(ntpServerIP); 384 | uint32_t beginWait = millis(); 385 | while (millis() - beginWait < 1500) { 386 | int size = Udp.parsePacket(); 387 | if (size >= NTP_PACKET_SIZE) { 388 | Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer 389 | unsigned long secsSince1900; 390 | // convert four bytes starting at location 40 to a long integer 391 | secsSince1900 = (unsigned long)packetBuffer[40] << 24; 392 | secsSince1900 |= (unsigned long)packetBuffer[41] << 16; 393 | secsSince1900 |= (unsigned long)packetBuffer[42] << 8; 394 | secsSince1900 |= (unsigned long)packetBuffer[43]; 395 | return secsSince1900 - 2208988800UL; 396 | } 397 | } 398 | return 0; // return 0 if unable to get the time 399 | } 400 | 401 | // send an NTP request to the time server at the given address 402 | void sendNTPpacket(IPAddress &address) 403 | { 404 | // set all bytes in the buffer to 0 405 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 406 | // Initialize values needed to form NTP request 407 | // (see URL above for details on the packets) 408 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 409 | packetBuffer[1] = 0; // Stratum, or type of clock 410 | packetBuffer[2] = 6; // Polling Interval 411 | packetBuffer[3] = 0xEC; // Peer Clock Precision 412 | // 8 bytes of zero for Root Delay & Root Dispersion 413 | packetBuffer[12] = 49; 414 | packetBuffer[13] = 0x4E; 415 | packetBuffer[14] = 49; 416 | packetBuffer[15] = 52; 417 | // all NTP fields have been given values, now 418 | // you can send a packet requesting a timestamp: 419 | Udp.beginPacket(address, 123); //NTP requests are to port 123 420 | Udp.write(packetBuffer, NTP_PACKET_SIZE); 421 | Udp.endPacket(); 422 | } 423 | 424 | /***********************************************************************************/ 425 | // HTU21D methods 426 | 427 | void readSensor() { 428 | if (millis() - lastSensorUpdate < CONFIG_SENSOR_DELAY) { 429 | return; 430 | } 431 | PRINTS("readSensor\t"); 432 | 433 | // HTU21D library 434 | float humidity = sensor.readCompensatedHumidity() + HUMIDITY_CORRECTION; 435 | float celsTemp = sensor.readTemperature() + TEMPERATURE_CORRECTION; 436 | 437 | PRINT("Temp: %s C", celsTemp); 438 | PRINT("Hum: %s %", humidity); 439 | PRINTLN(); 440 | postData(celsTemp, humidity); 441 | lastSensorUpdate = millis(); 442 | } 443 | 444 | /***********************************************************************************/ 445 | // MQTT methods 446 | 447 | void callback(char* topic, byte* payload, unsigned int length) { 448 | PRINTS("New message arrived: "); 449 | PRINTLN(topic); 450 | if (strcmp(topic, light_set_topic) == 0) { 451 | PRINTLN("light callback"); 452 | callbackLight(topic, payload, length); 453 | } else { 454 | char message[length + 1]; 455 | for (int b = 0; b < length; b++) { 456 | message[b] = (char)payload[b]; 457 | } 458 | message[length] = '\0'; 459 | 460 | newMessageAvailable = processJson(message); 461 | PRINTS("New message arrived: "); 462 | PRINTLN(message); 463 | 464 | } 465 | } 466 | 467 | bool processJson(char* message) { 468 | StaticJsonBuffer jsonBuffer; 469 | 470 | JsonObject& root = jsonBuffer.parseObject(message); 471 | 472 | if (!root.success()) { 473 | PRINTS("parseObject() failed"); 474 | return false; 475 | } 476 | 477 | if (root.containsKey("message")) { 478 | strcpy(newMessage, root["message"]); 479 | PRINTS("new message "); 480 | PRINTLN(newMessage); 481 | } 482 | 483 | if (root.containsKey("repeat")) { 484 | PRINTS("---------------------- root.repeat "); 485 | repeatMessage = root["repeat"].as(); 486 | PRINTLN(repeatMessage); 487 | } 488 | 489 | bool hasPause = false; 490 | 491 | if (root.containsKey("textEffect")) { 492 | PRINTLN("textEffect"); 493 | JsonObject& textEffectObj = root["textEffect"]; 494 | if (textEffectObj.containsKey("in")) { 495 | effectIn = effects[textEffectObj["in"].as()]; 496 | } else { 497 | effectIn = effects[2]; // Scroll from right to left 498 | } 499 | 500 | if (textEffectObj.containsKey("out")) { 501 | effectOut = effects[textEffectObj["out"].as()]; 502 | } else { 503 | effectOut = effects[2]; // Scroll from right to left 504 | } 505 | 506 | if (textEffectObj.containsKey("pause")) { 507 | messagePause = textEffectObj["pause"].as(); 508 | hasPause = true; 509 | } 510 | 511 | } else { 512 | effectIn = effects[2]; 513 | effectOut = effects[2]; 514 | } 515 | P.setTextEffect(effectIn, effectOut); 516 | 517 | if (root.containsKey("frameDelay")) { 518 | frameDelay = root["frameDelay"].as(); 519 | } else { 520 | frameDelay = FRAME_DELAY_DEFAULT; 521 | } 522 | 523 | if (!hasPause) { 524 | messagePause = MESSAGE_PAUSE_DEFAULT; 525 | } 526 | 527 | return true; 528 | } 529 | 530 | void reconnect() { 531 | if (!client.connected()) { 532 | PRINTS("Attempting MQTT connection..."); 533 | client.connect(client_id, mqtt_username, mqtt_password); 534 | 535 | P.displayScroll("Connecting to MQTT", PA_CENTER, scrollEffect, FRAME_DELAY_DEFAULT); 536 | waitForMessageComplete(false); 537 | 538 | // Attempt to connect 539 | if (client.connected()) { 540 | PRINTS("connected"); 541 | client.subscribe(message_topic); 542 | client.subscribe(command_topic); 543 | client.subscribe(light_set_topic); 544 | 545 | P.displayScroll("MQTT connected", PA_CENTER, scrollEffect, FRAME_DELAY_DEFAULT); 546 | waitForMessageComplete(false); 547 | 548 | } else { 549 | PRINTS("failed, rc="); 550 | PRINTLN(client.state()); 551 | } 552 | machineState = 6; 553 | } 554 | } 555 | 556 | void postData(float tempCelsius, float humidityPercent) { 557 | if (!client.connected()) { 558 | reconnect(); 559 | } else { 560 | char buf[10]; 561 | if (tempCelsius != publishedTemperature) { 562 | dtostrf(tempCelsius, 0, 0, buf); 563 | client.publish(CONFIG_MQTT_TOPIC_TEMP, buf); 564 | publishedTemperature = tempCelsius; 565 | } 566 | 567 | if (humidityPercent != publishedHumidity) { 568 | dtostrf(humidityPercent, 0, 0, buf); 569 | client.publish(CONFIG_MQTT_TOPIC_HUMIDITY, buf); 570 | publishedHumidity = humidityPercent; 571 | } 572 | } 573 | } 574 | 575 | 576 | /***********************************************************************************/ 577 | // Utility methods 578 | 579 | void handleWifi(bool displayErr) { 580 | char* errMessage; 581 | byte count = 0; 582 | while (WiFi.status() != WL_CONNECTED) 583 | { 584 | 585 | if (++count > 10) { 586 | ESP.restart(); 587 | } 588 | 589 | digitalWrite(LED_PIN, LOW); 590 | 591 | char* err = err2Str(WiFi.status()); 592 | PRINT("\n", err); 593 | PRINTLN(""); 594 | 595 | if (P.displayAnimate() && displayErr) { 596 | sprintf(errMessage, "Error: %s", err); 597 | P.displayScroll(errMessage, PA_CENTER, scrollEffect, FRAME_DELAY_DEFAULT); 598 | } 599 | yield(); 600 | 601 | digitalWrite(LED_PIN, HIGH); 602 | 603 | if (WiFi.status() != WL_CONNECTED) { 604 | PRINTLN("Retrying in .5s"); 605 | delay(500); 606 | } 607 | 608 | } 609 | } 610 | 611 | char *err2Str(wl_status_t code) { 612 | switch (code) 613 | { 614 | case WL_IDLE_STATUS: return ("IDLE"); break; // WiFi is in process of changing between statuses 615 | case WL_NO_SSID_AVAIL: return ("NO_SSID_AVAIL"); break; // case configured SSID cannot be reached 616 | case WL_CONNECTED: return ("CONNECTED"); break; // successful connection is established 617 | case WL_CONNECT_FAILED: return ("WRONG_PASSWORD"); break; // password is incorrect 618 | case WL_DISCONNECTED: return ("CONNECT_FAILED"); break; // module is not configured in station mode 619 | default: return ("??"); 620 | } 621 | } 622 | 623 | void waitForMessageComplete(bool blinkStatusLed) { 624 | int a = 0; 625 | PRINTLN(" "); 626 | bool state = 0; 627 | while (!P.displayAnimate()) { 628 | a++; 629 | if (a % 2500 == 0) { 630 | PRINTS("."); 631 | state = ! state; 632 | if (blinkStatusLed) { 633 | digitalWrite(LED_PIN, state); 634 | } 635 | } 636 | yield(); 637 | ArduinoOTA.handle(); 638 | } 639 | } 640 | 641 | void configureOTA() { 642 | ArduinoOTA.onStart([]() { 643 | PRINTS("OTA Start"); 644 | P.displayScroll("Updating Firmware", PA_CENTER, scrollEffect, FRAME_DELAY_DEFAULT); 645 | waitForMessageComplete(false); 646 | }); 647 | ArduinoOTA.onEnd([]() { 648 | PRINTS("\nEnd"); 649 | P.displayScroll("Firmware Updated!", PA_CENTER, scrollEffect, FRAME_DELAY_DEFAULT); 650 | waitForMessageComplete(false); 651 | }); 652 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 653 | char* m = "-"; 654 | if (P.displayAnimate()) { 655 | int percent = (progress / (total / 100)); 656 | char* prog = "-/|\\"; 657 | m[0] = prog[percent % 4]; 658 | P.displayText(m, PA_CENTER, 10, 10, PA_PRINT); 659 | } 660 | PRINT("Progress: %u%%\r", (progress / (total / 100))); 661 | }); 662 | ArduinoOTA.onError([](ota_error_t error) { 663 | PRINT("Error[%u]: ", error); 664 | if (error == OTA_AUTH_ERROR) PRINTS("Auth Failed"); 665 | else if (error == OTA_BEGIN_ERROR) PRINTS("Begin Failed"); 666 | else if (error == OTA_CONNECT_ERROR) PRINTS("Connect Failed"); 667 | else if (error == OTA_RECEIVE_ERROR) PRINTS("Receive Failed"); 668 | else if (error == OTA_END_ERROR) PRINTS("End Failed"); 669 | }); 670 | ArduinoOTA.setHostname(host_name); 671 | ArduinoOTA.begin(); 672 | } 673 | 674 | /***********************************************************************************/ 675 | // WS2812 Methods 676 | 677 | /********************************** GLOBALS for EFFECTS ******************************/ 678 | //RAINBOW 679 | uint8_t thishue = 0; // Starting hue value. 680 | uint8_t deltahue = 10; 681 | 682 | //CANDYCANE 683 | CRGBPalette16 currentPalettestriped; //for Candy Cane 684 | CRGBPalette16 gPal; //for fire 685 | 686 | //NOISE 687 | static uint16_t dist; // A random number for our noise generator. 688 | uint16_t scale = 30; // Wouldn't recommend changing this on the fly, or the animation will be really blocky. 689 | uint8_t maxChanges = 48; // Value for blending between palettes. 690 | CRGBPalette16 targetPalette(OceanColors_p); 691 | CRGBPalette16 currentPalette(CRGB::Black); 692 | 693 | //TWINKLE 694 | #define DENSITY 80 695 | int twinklecounter = 0; 696 | 697 | //RIPPLE 698 | uint8_t colour; // Ripple colour is randomized. 699 | int center = 0; // Center of the current ripple. 700 | int step = -1; // -1 is the initializing step. 701 | uint8_t myfade = 255; // Starting brightness. 702 | #define maxsteps 16 // Case statement wouldn't allow a variable. 703 | uint8_t bgcol = 0; // Background colour rotates. 704 | int thisdelay = 20; // Standard delay value. 705 | 706 | //DOTS 707 | uint8_t count = 0; // Count up to 255 and then reverts to 0 708 | uint8_t fadeval = 224; // Trail behind the LED's. Lower => faster fade. 709 | uint8_t bpm = 30; 710 | 711 | //LIGHTNING 712 | uint8_t frequency = 50; // controls the interval between strikes 713 | uint8_t flashes = 8; //the upper limit of flashes per strike 714 | unsigned int dimmer = 1; 715 | uint8_t ledstart; // Starting location of a flash 716 | uint8_t ledlen; 717 | int lightningcounter = 0; 718 | 719 | //FUNKBOX 720 | int idex = 0; //-LED INDEX (0 to WS_LENGTH-1 721 | int TOP_INDEX = int(WS_LENGTH / 2); 722 | int thissat = 255; //-FX LOOPS DELAY VAR 723 | uint8_t thishuepolice = 0; 724 | int antipodal_index(int i) { 725 | int iN = i + TOP_INDEX; 726 | if (i >= TOP_INDEX) { 727 | iN = ( i + TOP_INDEX ) % WS_LENGTH; 728 | } 729 | return iN; 730 | } 731 | 732 | //FIRE 733 | #define COOLING 55 734 | #define SPARKING 120 735 | bool gReverseDirection = false; 736 | 737 | //BPM 738 | uint8_t gHue = 0; 739 | 740 | struct CRGB leds[WS_LENGTH]; 741 | 742 | /********************************** START SETUP*****************************************/ 743 | void configureLedStrip() { 744 | 745 | FastLED.addLeds(leds, WS_LENGTH); 746 | 747 | setupStripedPalette( CRGB::Red, CRGB::Red, CRGB::White, CRGB::White); //for CANDY CANE 748 | 749 | gPal = HeatColors_p; //for FIRE 750 | 751 | setColor(0, 0, 0); // Blank the LED strip 752 | } 753 | 754 | void callbackLight(char* topic, byte* payload, unsigned int len) { 755 | digitalWrite(LED_PIN, LOW); 756 | char message[len + 1]; 757 | for (int i = 0; i < len; i++) { 758 | message[i] = (char)payload[i]; 759 | } 760 | message[len] = '\0'; 761 | 762 | if (!processLightJson(message)) { 763 | return; 764 | } 765 | 766 | if (stateOn) { 767 | 768 | realRed = map(stripRed, 0, 255, 0, stripBrightness); 769 | realGreen = map(stripGreen, 0, 255, 0, stripBrightness); 770 | realBlue = map(stripBlue, 0, 255, 0, stripBrightness); 771 | } 772 | else { 773 | 774 | realRed = 0; 775 | realGreen = 0; 776 | realBlue = 0; 777 | } 778 | 779 | startFade = true; 780 | inFade = false; // Kill the current fade 781 | 782 | sendState(); 783 | digitalWrite(LED_PIN, HIGH); 784 | } 785 | 786 | bool processLightJson(char* message) { 787 | StaticJsonBuffer jsonBuffer; 788 | 789 | JsonObject& root = jsonBuffer.parseObject(message); 790 | 791 | if (!root.success()) { 792 | return false; 793 | } 794 | 795 | if (root.containsKey("state")) { 796 | if (strcmp(root["state"], on_cmd) == 0) { 797 | stateOn = true; 798 | } 799 | else if (strcmp(root["state"], off_cmd) == 0) { 800 | stateOn = false; 801 | onbeforeflash = false; 802 | } 803 | } 804 | 805 | // If "flash" is included, treat RGB and brightness differently 806 | if (root.containsKey("flash")) { 807 | flashLength = (int)root["flash"] * 1000; 808 | 809 | oldstripEffectString = stripEffectString; 810 | 811 | if (root.containsKey("brightness")) { 812 | flashBrightness = root["brightness"]; 813 | } 814 | else { 815 | flashBrightness = stripBrightness; 816 | } 817 | 818 | if (root.containsKey("color")) { 819 | flashRed = root["color"]["r"]; 820 | flashGreen = root["color"]["g"]; 821 | flashBlue = root["color"]["b"]; 822 | } 823 | else { 824 | flashRed = stripRed; 825 | flashGreen = stripGreen; 826 | flashBlue = stripBlue; 827 | } 828 | 829 | if (root.containsKey("effect")) { 830 | stripEffect = root["effect"]; 831 | stripEffectString = stripEffect; 832 | twinklecounter = 0; //manage twinklecounter 833 | } 834 | 835 | if (root.containsKey("transition")) { 836 | transitionTime = root["transition"]; 837 | } 838 | else if ( stripEffectString == "solid") { 839 | transitionTime = 1; 840 | } 841 | 842 | flashRed = map(flashRed, 0, 255, 0, flashBrightness); 843 | flashGreen = map(flashGreen, 0, 255, 0, flashBrightness); 844 | flashBlue = map(flashBlue, 0, 255, 0, flashBrightness); 845 | 846 | flash = true; 847 | startFlash = true; 848 | } 849 | else { // Not flashing 850 | flash = false; 851 | 852 | if (stateOn) { //if the light is turned on and the light isn't flashing 853 | onbeforeflash = true; 854 | } 855 | 856 | if (root.containsKey("color")) { 857 | stripRed = root["color"]["r"]; 858 | stripGreen = root["color"]["g"]; 859 | stripBlue = root["color"]["b"]; 860 | } 861 | 862 | if (root.containsKey("color_temp")) { 863 | //temp comes in as mireds, need to convert to kelvin then to RGB 864 | int color_temp = root["color_temp"]; 865 | unsigned int kelvin = MILLION / color_temp; 866 | 867 | temp2rgb(kelvin); 868 | 869 | } 870 | 871 | if (root.containsKey("brightness")) { 872 | stripBrightness = root["brightness"]; 873 | } 874 | 875 | if (root.containsKey("effect")) { 876 | if (root.containsKey("notify_effect")) { 877 | oldstripEffectString = stripEffectString; 878 | revertToOldStripEffect = true; 879 | if (!stateOn) { 880 | revertToOff = true; 881 | stateOn = true; 882 | } 883 | } 884 | stripEffect = root["effect"]; 885 | stripEffectString = stripEffect; 886 | twinklecounter = 0; //manage twinklecounter 887 | } 888 | 889 | if (root.containsKey("transition")) { 890 | transitionTime = root["transition"]; 891 | } 892 | else if ( stripEffectString == "solid") { 893 | transitionTime = 0; 894 | } 895 | 896 | } 897 | 898 | return true; 899 | } 900 | 901 | void sendState() { 902 | StaticJsonBuffer jsonBuffer; 903 | 904 | JsonObject& root = jsonBuffer.createObject(); 905 | 906 | root["state"] = (stateOn) ? on_cmd : off_cmd; 907 | JsonObject& color = root.createNestedObject("color"); 908 | color["r"] = stripRed; 909 | color["g"] = stripGreen; 910 | color["b"] = stripBlue; 911 | 912 | root["brightness"] = stripBrightness; 913 | root["effect"] = stripEffectString.c_str(); 914 | 915 | 916 | char buffer[root.measureLength() + 1]; 917 | root.printTo(buffer, sizeof(buffer)); 918 | 919 | client.publish(light_state_topic, buffer, true); 920 | } 921 | 922 | void setColor(int inR, int inG, int inB) { 923 | for (int i = 0; i < WS_LENGTH; i++) { 924 | leds[i].red = inR; 925 | leds[i].green = inG; 926 | leds[i].blue = inB; 927 | } 928 | 929 | FastLED.show(); 930 | } 931 | 932 | void handleLedStrip() { 933 | //EFFECT BPM 934 | if (stripEffectString == "bpm") { 935 | uint8_t BeatsPerMinute = 62; 936 | CRGBPalette16 palette = PartyColors_p; 937 | uint8_t beat = beatsin8( BeatsPerMinute, 64, 255); 938 | for ( int i = 0; i < WS_LENGTH; i++) { //9948 939 | leds[i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 940 | } 941 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 942 | transitionTime = 30; 943 | } 944 | showleds(); 945 | } 946 | 947 | 948 | //EFFECT Candy Cane 949 | if (stripEffectString == "candy cane") { 950 | static uint8_t startIndex = 0; 951 | startIndex = startIndex + 1; /* higher = faster motion */ 952 | fill_palette( leds, WS_LENGTH, 953 | startIndex, 16, /* higher = narrower stripes */ 954 | currentPalettestriped, 255, LINEARBLEND); 955 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 956 | transitionTime = 0; 957 | } 958 | showleds(); 959 | } 960 | 961 | 962 | //EFFECT CONFETTI 963 | if (stripEffectString == "confetti" ) { 964 | fadeToBlackBy( leds, WS_LENGTH, 25); 965 | int pos = random16(WS_LENGTH); 966 | leds[pos] += CRGB(realRed + random8(64), realGreen, realBlue); 967 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 968 | transitionTime = 30; 969 | } 970 | showleds(); 971 | } 972 | 973 | 974 | //EFFECT CYCLON RAINBOW 975 | if (stripEffectString == "cyclon rainbow") { //Single Dot Down 976 | static uint8_t hue = 0; 977 | // First slide the led in one direction 978 | for (int i = 0; i < WS_LENGTH; i++) { 979 | // Set the i'th led to red 980 | leds[i] = CHSV(hue++, 255, 255); 981 | // Show the leds 982 | showleds(); 983 | // now that we've shown the leds, reset the i'th led to black 984 | // leds[i] = CRGB::Black; 985 | fadeall(); 986 | // Wait a little bit before we loop around and do it again 987 | delay(10); 988 | } 989 | for (int i = (WS_LENGTH) - 1; i >= 0; i--) { 990 | // Set the i'th led to red 991 | leds[i] = CHSV(hue++, 255, 255); 992 | // Show the leds 993 | showleds(); 994 | // now that we've shown the leds, reset the i'th led to black 995 | // leds[i] = CRGB::Black; 996 | fadeall(); 997 | // Wait a little bit before we loop around and do it again 998 | delay(10); 999 | } 1000 | } 1001 | 1002 | 1003 | //EFFECT DOTS 1004 | if (stripEffectString == "dots") { 1005 | uint8_t inner = beatsin8(bpm, WS_LENGTH / 4, WS_LENGTH / 4 * 3); 1006 | uint8_t outer = beatsin8(bpm, 0, WS_LENGTH - 1); 1007 | uint8_t middle = beatsin8(bpm, WS_LENGTH / 3, WS_LENGTH / 3 * 2); 1008 | leds[middle] = CRGB::Purple; 1009 | leds[inner] = CRGB::Blue; 1010 | leds[outer] = CRGB::Aqua; 1011 | nscale8(leds, WS_LENGTH, fadeval); 1012 | 1013 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1014 | transitionTime = 30; 1015 | } 1016 | showleds(); 1017 | } 1018 | 1019 | 1020 | //EFFECT FIRE 1021 | if (stripEffectString == "fire") { 1022 | Fire2012WithPalette(); 1023 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1024 | transitionTime = 150; 1025 | } 1026 | showleds(); 1027 | } 1028 | 1029 | random16_add_entropy( random8()); 1030 | 1031 | 1032 | //EFFECT Glitter 1033 | if (stripEffectString == "glitter") { 1034 | fadeToBlackBy( leds, WS_LENGTH, 20); 1035 | addGlitterColor(80, realRed, realGreen, realBlue); 1036 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1037 | transitionTime = 30; 1038 | } 1039 | showleds(); 1040 | } 1041 | 1042 | 1043 | //EFFECT JUGGLE 1044 | if (stripEffectString == "juggle" ) { // eight colored dots, weaving in and out of sync with each other 1045 | fadeToBlackBy(leds, WS_LENGTH, 20); 1046 | for (int i = 0; i < 8; i++) { 1047 | leds[beatsin16(i + 7, 0, WS_LENGTH - 1 )] |= CRGB(realRed, realGreen, realBlue); 1048 | } 1049 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1050 | transitionTime = 130; 1051 | } 1052 | showleds(); 1053 | } 1054 | 1055 | 1056 | //EFFECT LIGHTNING 1057 | if (stripEffectString == "lightning") { 1058 | twinklecounter = twinklecounter + 1; //Resets strip if previous animation was running 1059 | if (twinklecounter < 2) { 1060 | FastLED.clear(); 1061 | FastLED.show(); 1062 | } 1063 | ledstart = random8(WS_LENGTH); // Determine starting location of flash 1064 | ledlen = random8(WS_LENGTH - ledstart); // Determine length of flash (not to go beyond WS_LENGTH-1) 1065 | for (int flashCounter = 0; flashCounter < random8(3, flashes); flashCounter++) { 1066 | if (flashCounter == 0) dimmer = 5; // the brightness of the leader is scaled down by a factor of 5 1067 | else dimmer = random8(1, 3); // return strokes are brighter than the leader 1068 | fill_solid(leds + ledstart, ledlen, CHSV(255, 0, 255 / dimmer)); 1069 | showleds(); // Show a section of LED's 1070 | delay(random8(4, 10)); // each flash only lasts 4-10 milliseconds 1071 | fill_solid(leds + ledstart, ledlen, CHSV(255, 0, 0)); // Clear the section of LED's 1072 | showleds(); 1073 | if (flashCounter == 0) delay (130); // longer delay until next flash after the leader 1074 | delay(50 + random8(100)); // shorter delay between strokes 1075 | } 1076 | delay(random8(frequency) * 100); // delay between strikes 1077 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1078 | transitionTime = 0; 1079 | } 1080 | showleds(); 1081 | } 1082 | 1083 | 1084 | //EFFECT POLICE ALL 1085 | if (stripEffectString == "police all") { //POLICE LIGHTS (TWO COLOR SOLID) 1086 | idex++; 1087 | if (idex >= WS_LENGTH) { 1088 | idex = 0; 1089 | } 1090 | int idexR = idex; 1091 | int idexB = antipodal_index(idexR); 1092 | int thathue = (thishuepolice + 160) % 255; 1093 | leds[idexR] = CHSV(thishuepolice, thissat, 255); 1094 | leds[idexB] = CHSV(thathue, thissat, 255); 1095 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1096 | transitionTime = 30; 1097 | } 1098 | showleds(); 1099 | } 1100 | 1101 | //EFFECT POLICE ONE 1102 | if (stripEffectString == "police one") { 1103 | idex++; 1104 | if (idex >= WS_LENGTH) { 1105 | idex = 0; 1106 | } 1107 | int idexR = idex; 1108 | int idexB = antipodal_index(idexR); 1109 | int thathue = (thishuepolice + 160) % 255; 1110 | for (int i = 0; i < WS_LENGTH; i++ ) { 1111 | if (i == idexR) { 1112 | leds[i] = CHSV(thishuepolice, thissat, 255); 1113 | } 1114 | else if (i == idexB) { 1115 | leds[i] = CHSV(thathue, thissat, 255); 1116 | } 1117 | else { 1118 | leds[i] = CHSV(0, 0, 0); 1119 | } 1120 | } 1121 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1122 | transitionTime = 30; 1123 | } 1124 | showleds(); 1125 | } 1126 | 1127 | 1128 | //EFFECT RAINBOW 1129 | if (stripEffectString == "rainbow") { 1130 | // FastLED's built-in rainbow generator 1131 | static uint8_t starthue = 0; thishue++; 1132 | fill_rainbow(leds, WS_LENGTH, thishue, deltahue); 1133 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1134 | transitionTime = 130; 1135 | } 1136 | showleds(); 1137 | } 1138 | 1139 | 1140 | //EFFECT RAINBOW WITH GLITTER 1141 | if (stripEffectString == "rainbow with glitter") { // FastLED's built-in rainbow generator with Glitter 1142 | static uint8_t starthue = 0; 1143 | thishue++; 1144 | fill_rainbow(leds, WS_LENGTH, thishue, deltahue); 1145 | addGlitter(80); 1146 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1147 | transitionTime = 130; 1148 | } 1149 | showleds(); 1150 | } 1151 | 1152 | 1153 | //EFFECT SIENLON 1154 | if (stripEffectString == "sinelon") { 1155 | fadeToBlackBy( leds, WS_LENGTH, 20); 1156 | int pos = beatsin16(13, 0, WS_LENGTH - 1); 1157 | leds[pos] += CRGB(realRed, realGreen, realBlue); 1158 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1159 | transitionTime = 150; 1160 | } 1161 | showleds(); 1162 | } 1163 | 1164 | 1165 | //EFFECT TWINKLE 1166 | if (stripEffectString == "twinkle") { 1167 | twinklecounter = twinklecounter + 1; 1168 | if (twinklecounter < 2) { //Resets strip if previous animation was running 1169 | FastLED.clear(); 1170 | FastLED.show(); 1171 | } 1172 | const CRGB lightcolor(8, 7, 1); 1173 | for ( int i = 0; i < WS_LENGTH; i++) { 1174 | if ( !leds[i]) continue; // skip black pixels 1175 | if ( leds[i].r & 1) { // is red odd? 1176 | leds[i] -= lightcolor; // darken if red is odd 1177 | } else { 1178 | leds[i] += lightcolor; // brighten if red is even 1179 | } 1180 | } 1181 | if ( random8() < DENSITY) { 1182 | int j = random16(WS_LENGTH); 1183 | if ( !leds[j] ) leds[j] = lightcolor; 1184 | } 1185 | 1186 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1187 | transitionTime = 0; 1188 | } 1189 | showleds(); 1190 | } 1191 | 1192 | 1193 | EVERY_N_MILLISECONDS(10) { 1194 | 1195 | nblendPaletteTowardPalette(currentPalette, targetPalette, maxChanges); // FOR NOISE ANIMATIon 1196 | { 1197 | gHue++; 1198 | } 1199 | 1200 | //EFFECT NOISE 1201 | if (stripEffectString == "noise") { 1202 | for (int i = 0; i < WS_LENGTH; i++) { // Just onE loop to fill up the LED array as all of the pixels change. 1203 | uint8_t index = inoise8(i * scale, dist + i * scale) % 255; // Get a value from the noise function. I'm using both x and y axis. 1204 | leds[i] = ColorFromPalette(currentPalette, index, 255, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. 1205 | } 1206 | dist += beatsin8(10, 1, 4); // Moving along the distance (that random number we started out with). Vary it a bit with a sine wave. 1207 | // In some sketches, I've used millis() instead of an incremented counter. Works a treat. 1208 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1209 | transitionTime = 0; 1210 | } 1211 | showleds(); 1212 | } 1213 | 1214 | //EFFECT RIPPLE 1215 | if (stripEffectString == "ripple") { 1216 | for (int i = 0; i < WS_LENGTH; i++) leds[i] = CHSV(bgcol++, 255, 15); // Rotate background colour. 1217 | switch (step) { 1218 | case -1: // Initialize ripple variables. 1219 | center = random(WS_LENGTH); 1220 | colour = random8(); 1221 | step = 0; 1222 | break; 1223 | case 0: 1224 | leds[center] = CHSV(colour, 255, 255); // Display the first pixel of the ripple. 1225 | step ++; 1226 | break; 1227 | case maxsteps: // At the end of the ripples. 1228 | step = -1; 1229 | break; 1230 | default: // Middle of the ripples. 1231 | leds[(center + step + WS_LENGTH) % WS_LENGTH] += CHSV(colour, 255, myfade / step * 2); // Simple wrap from Marc Miller 1232 | leds[(center - step + WS_LENGTH) % WS_LENGTH] += CHSV(colour, 255, myfade / step * 2); 1233 | step ++; // Next step. 1234 | break; 1235 | } 1236 | if ((transitionTime == 0 or transitionTime == NULL) && !revertToOldStripEffect) { 1237 | transitionTime = 30; 1238 | } 1239 | showleds(); 1240 | } 1241 | 1242 | } 1243 | 1244 | 1245 | EVERY_N_SECONDS(5) { 1246 | targetPalette = CRGBPalette16(CHSV(random8(), 255, random8(128, 255)), CHSV(random8(), 255, random8(128, 255)), CHSV(random8(), 192, random8(128, 255)), CHSV(random8(), 255, random8(128, 255))); 1247 | } 1248 | 1249 | //FLASH AND FADE SUPPORT 1250 | if (flash) { 1251 | if (startFlash) { 1252 | startFlash = false; 1253 | flashStartTime = millis(); 1254 | } 1255 | 1256 | if ((millis() - flashStartTime) <= flashLength) { 1257 | if ((millis() - flashStartTime) % 1000 <= 500) { 1258 | setColor(flashRed, flashGreen, flashBlue); 1259 | } 1260 | else { 1261 | setColor(0, 0, 0); 1262 | // If you'd prefer the flashing to happen "on top of" 1263 | // the current color, uncomment the next line. 1264 | // setColor(realRed, realGreen, realBlue); 1265 | } 1266 | } 1267 | else { 1268 | flash = false; 1269 | stripEffectString = oldstripEffectString; 1270 | if (onbeforeflash) { //keeps light off after flash if light was originally off 1271 | setColor(realRed, realGreen, realBlue); 1272 | } 1273 | else { 1274 | stateOn = false; 1275 | setColor(0, 0, 0); 1276 | sendState(); 1277 | } 1278 | } 1279 | } 1280 | 1281 | if (startFade && stripEffectString == "solid") { 1282 | // If we don't want to fade, skip it. 1283 | if (transitionTime == 0) { 1284 | setColor(realRed, realGreen, realBlue); 1285 | 1286 | rdVal = realRed; 1287 | grnVal = realGreen; 1288 | bluVal = realBlue; 1289 | 1290 | startFade = false; 1291 | } 1292 | else { 1293 | loopCount = 0; 1294 | stepR = calculateStep(rdVal, realRed); 1295 | stepG = calculateStep(grnVal, realGreen); 1296 | stepB = calculateStep(bluVal, realBlue); 1297 | 1298 | inFade = true; 1299 | } 1300 | } 1301 | 1302 | if (inFade) { 1303 | startFade = false; 1304 | unsigned long now = millis(); 1305 | if (now - lastLoop > transitionTime) { 1306 | if (loopCount <= 1020) { 1307 | lastLoop = now; 1308 | 1309 | rdVal = calculateVal(stepR, rdVal, loopCount); 1310 | grnVal = calculateVal(stepG, grnVal, loopCount); 1311 | bluVal = calculateVal(stepB, bluVal, loopCount); 1312 | 1313 | if (stripEffectString == "solid") { 1314 | setColor(rdVal, grnVal, bluVal); // Write current values to LED pins 1315 | } 1316 | loopCount++; 1317 | } 1318 | else { 1319 | inFade = false; 1320 | } 1321 | } 1322 | } 1323 | } 1324 | 1325 | /**************************** START TRANSITION FADER *****************************************/ 1326 | // From https://www.arduino.cc/en/Tutorial/ColorCrossfader 1327 | /* BELOW THIS LINE IS THE MATH -- YOU SHOULDN'T NEED TO CHANGE THIS FOR THE BASICS 1328 | The program works like this: 1329 | Imagine a crossfade that moves the red LED from 0-10, 1330 | the green from 0-5, and the blue from 10 to 7, in 1331 | ten steps. 1332 | We'd want to count the 10 steps and increase or 1333 | decrease color values in evenly stepped increments. 1334 | Imagine a + indicates raising a value by 1, and a - 1335 | equals lowering it. Our 10 step fade would look like: 1336 | 1 2 3 4 5 6 7 8 9 10 1337 | R + + + + + + + + + + 1338 | G + + + + + 1339 | B - - - 1340 | The red rises from 0 to 10 in ten steps, the green from 1341 | 0-5 in 5 steps, and the blue falls from 10 to 7 in three steps. 1342 | In the real program, the color percentages are converted to 1343 | 0-255 values, and there are 1020 steps (255*4). 1344 | To figure out how big a step there should be between one up- or 1345 | down-tick of one of the LED values, we call calculateStep(), 1346 | which calculates the absolute gap between the start and end values, 1347 | and then divides that gap by 1020 to determine the size of the step 1348 | between adjustments in the value. 1349 | */ 1350 | int calculateStep(int prevValue, int endValue) { 1351 | int step = endValue - prevValue; // What's the overall gap? 1352 | if (step) { // If its non-zero, 1353 | step = 1020 / step; // divide by 1020 1354 | } 1355 | 1356 | return step; 1357 | } 1358 | /* The next function is calculateVal. When the loop value, i, 1359 | reaches the step size appropriate for one of the 1360 | colors, it increases or decreases the value of that color by 1. 1361 | (R, G, and B are each calculated separately.) 1362 | */ 1363 | int calculateVal(int step, int val, int i) { 1364 | if ((step) && i % step == 0) { // If step is non-zero and its time to change a value, 1365 | if (step > 0) { // increment the value if step is positive... 1366 | val += 1; 1367 | } 1368 | else if (step < 0) { // ...or decrement it if step is negative 1369 | val -= 1; 1370 | } 1371 | } 1372 | 1373 | // Defensive driving: make sure val stays in the range 0-255 1374 | if (val > 255) { 1375 | val = 255; 1376 | } 1377 | else if (val < 0) { 1378 | val = 0; 1379 | } 1380 | 1381 | return val; 1382 | } 1383 | 1384 | 1385 | 1386 | /**************************** START STRIPLED PALETTE *****************************************/ 1387 | void setupStripedPalette( CRGB A, CRGB AB, CRGB B, CRGB BA) { 1388 | currentPalettestriped = CRGBPalette16( 1389 | A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B 1390 | // A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B 1391 | ); 1392 | } 1393 | 1394 | 1395 | 1396 | /********************************** START FADE************************************************/ 1397 | void fadeall() { 1398 | for (int i = 0; i < WS_LENGTH; i++) { 1399 | leds[i].nscale8(250); //for CYCLon 1400 | } 1401 | } 1402 | 1403 | 1404 | 1405 | /********************************** START FIRE **********************************************/ 1406 | void Fire2012WithPalette() 1407 | { 1408 | // Array of temperature readings at each simulation cell 1409 | static byte heat[WS_LENGTH]; 1410 | 1411 | // Step 1. Cool down every cell a little 1412 | for ( int i = 0; i < WS_LENGTH; i++) { 1413 | heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / WS_LENGTH) + 2)); 1414 | } 1415 | 1416 | // Step 2. Heat from each cell drifts 'up' and diffuses a little 1417 | for ( int k = WS_LENGTH - 1; k >= 2; k--) { 1418 | heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; 1419 | } 1420 | 1421 | // Step 3. Randomly ignite new 'sparks' of heat near the bottom 1422 | if ( random8() < SPARKING ) { 1423 | int y = random8(7); 1424 | heat[y] = qadd8( heat[y], random8(160, 255) ); 1425 | } 1426 | 1427 | // Step 4. Map from heat cells to LED colors 1428 | for ( int j = 0; j < WS_LENGTH; j++) { 1429 | // Scale the heat value from 0-255 down to 0-240 1430 | // for best results with color palettes. 1431 | byte colorindex = scale8( heat[j], 240); 1432 | CRGB color = ColorFromPalette( gPal, colorindex); 1433 | int pixelnumber; 1434 | if ( gReverseDirection ) { 1435 | pixelnumber = (WS_LENGTH - 1) - j; 1436 | } else { 1437 | pixelnumber = j; 1438 | } 1439 | leds[pixelnumber] = color; 1440 | } 1441 | } 1442 | 1443 | 1444 | 1445 | /********************************** START ADD GLITTER *********************************************/ 1446 | void addGlitter( fract8 chanceOfGlitter) 1447 | { 1448 | if ( random8() < chanceOfGlitter) { 1449 | leds[ random16(WS_LENGTH) ] += CRGB::White; 1450 | } 1451 | } 1452 | 1453 | 1454 | 1455 | /********************************** START ADD GLITTER COLOR ****************************************/ 1456 | void addGlitterColor( fract8 chanceOfGlitter, int stripRed, int stripGreen, int stripBlue) 1457 | { 1458 | if ( random8() < chanceOfGlitter) { 1459 | leds[ random16(WS_LENGTH) ] += CRGB(stripRed, stripGreen, stripBlue); 1460 | } 1461 | } 1462 | 1463 | 1464 | 1465 | /********************************** START SHOW LEDS ***********************************************/ 1466 | void showleds() { 1467 | 1468 | delay(1); 1469 | 1470 | if (stateOn) { 1471 | FastLED.setBrightness(stripBrightness); //EXECUTE EFFECT COLOR 1472 | FastLED.show(); 1473 | if (transitionTime > 0 && transitionTime < 130) { //Sets animation speed based on receieved value 1474 | FastLED.delay(1000 / transitionTime); 1475 | //delay(10*transitionTime); 1476 | } 1477 | } 1478 | else if (startFade) { 1479 | setColor(0, 0, 0); 1480 | startFade = false; 1481 | } 1482 | } 1483 | void temp2rgb(unsigned int kelvin) { 1484 | int tmp_internal = kelvin / 100.0; 1485 | 1486 | // red 1487 | if (tmp_internal <= 66) { 1488 | stripRed = 255; 1489 | } else { 1490 | float tmp_stripRed = 329.698727446 * pow(tmp_internal - 60, -0.1332047592); 1491 | if (tmp_stripRed < 0) { 1492 | stripRed = 0; 1493 | } else if (tmp_stripRed > 255) { 1494 | stripRed = 255; 1495 | } else { 1496 | stripRed = tmp_stripRed; 1497 | } 1498 | } 1499 | 1500 | // green 1501 | if (tmp_internal <= 66) { 1502 | float tmp_stripGreen = 99.4708025861 * log(tmp_internal) - 161.1195681661; 1503 | if (tmp_stripGreen < 0) { 1504 | stripGreen = 0; 1505 | } else if (tmp_stripGreen > 255) { 1506 | stripGreen = 255; 1507 | } else { 1508 | stripGreen = tmp_stripGreen; 1509 | } 1510 | } else { 1511 | float tmp_stripGreen = 288.1221695283 * pow(tmp_internal - 60, -0.0755148492); 1512 | if (tmp_stripGreen < 0) { 1513 | stripGreen = 0; 1514 | } else if (tmp_stripGreen > 255) { 1515 | stripGreen = 255; 1516 | } else { 1517 | stripGreen = tmp_stripGreen; 1518 | } 1519 | } 1520 | 1521 | // blue 1522 | if (tmp_internal >= 66) { 1523 | stripBlue = 255; 1524 | } else if (tmp_internal <= 19) { 1525 | stripBlue = 0; 1526 | } else { 1527 | float tmp_stripBlue = 138.5177312231 * log(tmp_internal - 10) - 305.0447927307; 1528 | if (tmp_stripBlue < 0) { 1529 | stripBlue = 0; 1530 | } else if (tmp_stripBlue > 255) { 1531 | stripBlue = 255; 1532 | } else { 1533 | stripBlue = tmp_stripBlue; 1534 | } 1535 | } 1536 | } 1537 | -------------------------------------------------------------------------------- /configuration.yaml: -------------------------------------------------------------------------------- 1 | # Sample configuration.yaml entry for the message board WS2812B LEDs 2 | light: 3 | - platform: mqtt 4 | schema: json 5 | name: "Message Board" 6 | state_topic: "messageboard/ledstrip" 7 | command_topic: "messageboard/ledstrip/set" 8 | effect: true 9 | effect_list: 10 | - solid 11 | - bpm 12 | - candy cane 13 | - confetti 14 | - cyclon rainbow 15 | - dots 16 | - fire 17 | - glitter 18 | - juggle 19 | - lightning 20 | - noise 21 | - police all 22 | - police one 23 | - rainbow 24 | - rainbow with glitter 25 | - ripple 26 | - sinelon 27 | - twinkle 28 | brightness: true 29 | flash: true 30 | rgb: true 31 | optimistic: false 32 | qos: 0 33 | -------------------------------------------------------------------------------- /example_config.h: -------------------------------------------------------------------------------- 1 | /* Sample configuration file. You will want to re-name or clone this file, and call it config.h 2 | * 3 | * For a generic ESP8266 module, ensure that flash memory is configured properly. For OTA support, a 4 | * profile of 1 MB (128k SPIFFS) has been tested to work properly. 5 | * 6 | * To control the WS2812B strip using Home Assistant, your configuration.yaml file should look like the example 7 | * included in the repository. 8 | * 9 | */ 10 | 11 | #define DEBUG 0 // Enables/disables debugging to the serial terminal. Not available over WiFi. 12 | #define USE_SENSOR 1 // Enable use of SI7021 or HTU21D sensor on I2C bus 13 | 14 | // WiFi Credentials 15 | #define CONFIG_WIFI_SSID "[REDACTED]" // Change this to your WiFi SSID 16 | #define CONFIG_WIFI_PASS "[REDACTED]" // Change to your WiFi password 17 | 18 | // NTP definitions 19 | #define NTP_SERVER_POOL "ca.pool.ntp.org" // Canada. See http://www.pool.ntp.org/en/ to find a different server pool 20 | #define NTP_OFFSET -14400 // Eastern Time offset in minutes 21 | #define NTP_UPDATE_EVERY_N_MILLISECONDS 60000 // How long between clock updates? 22 | 23 | // MQTT Connection Definitions 24 | #define CONFIG_MQTT_HOST "[REDACTED]" // MQTT host IP address 25 | #define CONFIG_MQTT_USER "[REDACTED]" // MQTT user name 26 | #define CONFIG_MQTT_PASS "[REDACTED]" // MQTT password 27 | #define CONFIG_HOST_NAME "message_board" // For mDNS and OTA identification. 28 | #define CONFIG_MQTT_CLIENT_ID String(ESP.getChipId()).c_str(); 29 | 30 | #define MAX_DEVICES 4 // MAX7219 Definitions. See https://majicdesigns.github.io/MD_MAX72XX/ for information 31 | 32 | // Pin assignments for Wemos D1 Mini 33 | #define CLK_PIN D5 // SCK 34 | #define DATA_PIN D7 // MOSI 35 | #define CS_PIN D8 // CS 36 | #define WS_PIN D3 // WS2812 37 | #define LED_PIN LED_BUILTIN 38 | 39 | // Pin assignments for Generic ESP8266 module 40 | //#define CLK_PIN 14 // SCK 41 | //#define DATA_PIN 13 // MOSI 42 | //#define CS_PIN 16 // CS 43 | //#define WS_PIN 12 // WS2812 44 | //#define LED_PIN 2 45 | 46 | // MQTT Topics 47 | #define CONFIG_MQTT_TOPIC_MESSAGE "messageboard/messages" // topic containing JSON object 48 | #define CONFIG_MQTT_TOPIC_COMMAND "messageboard/command" // topic containing commands for the message board. 49 | #define CONFIG_MQTT_TOPIC_TEMP "messageboard/sensor/temperature" // topic to publish temperature data 50 | #define CONFIG_MQTT_TOPIC_HUMIDITY "messageboard/sensor/humidity" // topic to publish humidity data 51 | #define CONFIG_MQTT_LIGHT_STATE "messageboard/ledstrip" // MQTT_JSON light topic 52 | #define CONFIG_MQTT_LIGHT_SET "messageboard/ledstrip/set" // MQTT_JSON set light topic 53 | 54 | // WS2812B Definitions 55 | 56 | #define WS_LENGTH 19 // Number of WS2812 LEDs 57 | #define CHIPSET WS2812B // Chipset of the LED strip 58 | #define COLOR_ORDER GRB // Colour order. 59 | #define CONFIG_ON_CMD "ON" // MQTT payload to turn lights on 60 | #define CONFIG_OFF_CMD "OFF" // MQTT payload to turn lights off 61 | 62 | // Global settings 63 | #define CONFIG_BUF_SIZE 512 64 | #define CONFIG_SENSOR_DELAY 60000 // Milliseconds between sensor readings 65 | #define MESSAGE_BRIGHTNESS_DEFAULT 5 // Default brightness of MAX7219 when displaying a message 66 | #define CLOCK_BRIGHTNESS_DEFAULT 1 // Default brightness of MAX7219 when displaying time 67 | #define FRAME_DELAY_DEFAULT 25 // Default animation speed of MAX7219 68 | #define MESSAGE_DELAY_DEFAULT 10000 // Minimum milliseconds between message display 69 | #define MESSAGE_REPEAT_DEFAULT 3 // Number of times to repeat the message unless otherwise specified. 70 | #define CONFIG_DEFAULT_EFFECT PA_SCROLL_LEFT // The default text effect for displaying messages 71 | #define MESSAGE_PAUSE_DEFAULT 0 // The time to pause for each message display. For scrolling defaults, recommended to leave at 0 72 | #define MILLION 1000000 // Required for library 73 | 74 | #if USE_SENSOR 75 | #define si7021Addr 0x40 // Default I2C address of HTU21D breakout 76 | #define TEMPERATURE_CORRECTION 0 // Correction factor for measured temperature. 77 | #define HUMIDITY_CORRECTION 0 // Correction factor for measured humidity. 78 | 79 | #endif 80 | 81 | // Debugging print statements 82 | // DO NOT MODIFY unless you know what you're doing! 83 | #if DEBUG 84 | #define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); } 85 | #define PRINTS(x) Serial.print(F(x)) 86 | #define PRINTX(x) Serial.println(x, HEX) 87 | #define PRINTLN(x) Serial.println(x) 88 | #else 89 | #define PRINT(s, x) 90 | #define PRINTS(x) 91 | #define PRINTX(x) 92 | #define PRINTLN(x) 93 | #endif 94 | 95 | -------------------------------------------------------------------------------- /images/clock-message-board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jptrsn/clock-message-board/6050d5f50ff4d304ad741d5f2df0d6bd8843301d/images/clock-message-board.jpg -------------------------------------------------------------------------------- /sensors.yaml: -------------------------------------------------------------------------------- 1 | # Sample entry for MQTT Sensors 2 | sensor: 3 | - platform: mqtt 4 | name: 'message_board_temp' 5 | state_topic: 'messageboard/temperature' 6 | unit_of_measurement: '°C' 7 | 8 | - platform: mqtt 9 | name: 'message_board_humidity' 10 | state_topic: 'messageboard/humidity' 11 | unit_of_measurement: '%' 12 | --------------------------------------------------------------------------------