├── 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 | 
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 |
--------------------------------------------------------------------------------