From 64816d8c0d9746795dae80a7f74afcc2fb45d6ba Mon Sep 17 00:00:00 2001 From: Norm Rasmussen Date: Mon, 16 Oct 2023 11:35:05 -0400 Subject: [PATCH] Added the ESP Medicine post for a Monday morning release. --- content/posts/medicine_indicator_light.md | 240 ++++++++++++++-------- 1 file changed, 149 insertions(+), 91 deletions(-) diff --git a/content/posts/medicine_indicator_light.md b/content/posts/medicine_indicator_light.md index 22be11c..035ef58 100644 --- a/content/posts/medicine_indicator_light.md +++ b/content/posts/medicine_indicator_light.md @@ -1,11 +1,11 @@ --- title: 'ESP8266 Medicine Indicator Light' -date: 2023-10-13T15:49:14-04:00 +date: 2023-10-16T11:34:14-04:00 tags: ["diy", "tutorial", "esp"] author: "Me" showToc: true TocOpen: false -draft: true +draft: false hidemeta: false description: 'Learn how to make an ESP8266 and a few simple components into an indicator light.' disableHLJS: true @@ -28,18 +28,23 @@ cover: --- This is a quick treat! We recently learned that one of our children needs to take medicine twice a day for the foreseeable -future. He's too young to take it on his own, and the morning and evening responsibility are split up between my partner and -I, so we needed some sort of indicator to let the other know if the previous dose was (or wasn't!) taken. Naturally, I sprung -into action with an ESP device and some leftover components. +future. He's too young to take it on his own, so the twice-a-day responsibility is split up between my partner and +I. However, sometimes our schedules don't overlap so succinctly, so we needed some sort of indicator to let the other know if +the previous dose was (or wasn't!) taken. Naturally, I sprung into action with an ESP device and components. ## Device Overview -The device works like this (see the pictures below): Two [single neopixel LEDs](https://www.adafruit.com/product/1260) and a -non-latching button are connected to the ESP8266. Inside the code there is a switch-case section which changes case per -button press. On each press, the light will change depending on what is setup for that case. At the same time, each case is -establishing a value for `morning` and `evening` which will be given a value of 0 or 1 - 0 to indicate that the medicine was -not taken, and 1 to indicate that it was taken. Along with the time server data, I send an MQTT payload to my [MQTT Broker, -Mosquitto](https://mosquitto.org/). +The device works like this ([see the pictures below]({{< ref "medicine_indicator_light.md#pictures" >}})): Two [single neopixel LEDs](https://www.adafruit.com/product/1260) and a +non-latching button are connected to the ESP8266. Inside the code there is a [switch-case](https://www.arduino.cc/reference/tr/language/structure/control-structure/switchcase/) +section which changes states per high/low state of the button. On each press, the lights will change to one of the following: + +* Red/Red +* Green/Red +* Red/Green +* Green/Green + +For each case, I established a values for `morning` and `evening` which will be given a value of 0 or 1 - 0 to indicate that the medicine was +not taken, and 1 to indicate that it was taken. Along with the time server data, I send an MQTT payload to my [MQTT Broker](https://mosquitto.org/). Here's what the payload looks like: @@ -53,27 +58,28 @@ Here's what the payload looks like: ## Thoughts and Background -I'd like to write a longer post that chops up to the code and goes into explanations of each section, but I'm running out of +I'd like to write a longer post that explains each section of the code, but I'm running out of time - work has been quite busy lately. If you'd like me to add a post with more code explanations, or just have questions, feel free to mention me on [Mastoton/Fosstodon](https://fosstodon.org/@notnorm) and we can chat. -The much shorter explanation is that I didn't want this device to be an isolated device in my network. So what I did was -connected the ESP8266 to my wifi network and import an NTP (Network Time Protocol) server to ensure I get I have the current -time. Then, look at the state of the light, and construct and transmit an MQTT payload with the state of the light & time, so -that I can use it elsewhere. +The much shorter explanation is that I didn't want this device to be an isolated device in my network; this was part of the +impetus in using an ESP device as opposed to another microcontroller. So what I did was connected the ESP8266 to my wifi +network and imported an NTP (Network Time Protocol) server to ensure I get I have the current time. Then, look at the state +given by the case (and thus the external light indicator), and construct and transmit an MQTT payload with the state of the +light & time, so that I can use it elsewhere. In other words, now with MQTT payloads being accessible to HomeAssistant, I can +send myself a notification, or make an announcement on a speaker. What's neat about importing a time server is that at midnight, I reset both of the lights to red so that I don't have to reset it manually when I get up in the morning. In a separate post, I'll show how I use the MQTT payload in HomeAssistant to send [Pushover Notifications](https://pushover.net) -to me and my partner in case we forgot to give my child his medicine! What's great about this little project is that it is -scalable (add more components and sensors!) and that it doesn't need to indicate if someone has taken their medicine! You -could use the button to count how many times your dog (or child!) has gone to the bathroom, how many times to eat a snack or +to me and my partner in case we forgot to give my child the medicine! What's great about this little project is that it is +scalable (add more components and sensors!) and it isn't limited to a medicine indicator. You +could use the button to count how many times your dog (or child!) has gone to the bathroom, how many times you have eat or drink water while working, and many more ideas. -Assuming everything has gone well for the day and we've given my child both of his doses, we should -be heading to bed with both of those lights being green. However, we'll want to take into account a few edge cases. See the -pictures below with quick explanations. +Assuming everything has gone well for the day and we've given my child both doses, we should be heading to bed with both of +those lights being green. ## Materials Used @@ -89,10 +95,14 @@ Here is what I used to construct this: ## Pictures -![Front of Device with one light green, the other red](../posts/img/esp_indicator_1.png) -![Front of Device with both lights green](../posts/img/esp_indicator_2.png) -![Front of Device with both lights red](../posts/img/esp_indicator_3.png) -![Underside of device with wires](../posts/img/esp_indicator_wires.png) +
+ + +
+
+ + +
## Full Code @@ -107,7 +117,7 @@ Here is what I used to construct this: #include #define NEO_PIN 14 // Pin for all the Neopixels -#define SIG_PIX 2 // Number of temperature pixels +#define SIG_PIX 2 // Data pin for Neopixels #define BUTTON_FWD_PIN 4 #define mqtt_topic "home/medicine" @@ -115,15 +125,15 @@ const char* ssid = WIFI_SSID; const char* password = WIFI_PASS; const char* mqtt_server = MQTT_SERV; const char* mqtt_user = MQTT_USER; -const char* mqtt_password = MQTT_PASS; +const char* mqtt_password = MQTT_PASSWORD; boolean oldState = HIGH; int mode = 0; // Active mode on startup +unsigned long previousMillis = 0; +const long interval = 5000; // NTP Definition -// const long utcOffsetInSeconds = -18000; const long utcOffsetInSeconds = -14400; -//char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "north-america.pool.ntp.org", utcOffsetInSeconds); @@ -134,27 +144,34 @@ long lastMsg = 0; char msg[50]; float value = 0; -char morning_status[] = "false"; -char evening_status[] = "false"; +// Setup defaults for the eventual MQTT payload +char hours[] = ""; +char minutes[] = ""; +boolean morning_status = false; +boolean evening_status = false; char current_time[] = ""; +int int_time = 0; +// Necessary Setup for Neopixels Adafruit_NeoPixel NeoJewel = Adafruit_NeoPixel(SIG_PIX, NEO_PIN, NEO_GRB + NEO_KHZ800); unsigned long delayTime; // Taken Medicine Color uint32_t pineGreen = NeoJewel.Color(14, 170, 26); + // Not Yet Taken Color uint32_t pureRed = NeoJewel.Color(255, 0, 0); void setup() { - delayTime = 1000; + //delayTime = 1000; Serial.begin(9600); - delay(500); + delay(50); while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only + // wait for serial port to connect. Needed for native USB port only } + // Print Status of Wifi Connection to Serial Monitor Serial.println("Attempting to connect to SSID: "); Serial.print(ssid); WiFi.begin(ssid, password); @@ -172,11 +189,14 @@ void setup() { Serial.println("Mac Address: "); Serial.println(WiFi.macAddress()); delay(100); - client.setServer(MQTT_SERV, MQTT_PORT); + client.setServer(mqtt_server, 1883); WiFi.setAutoReconnect(true); WiFi.persistent(true); - // Necessary code for NeoPixel setup + // Initialize Time + timeClient.begin(); + + // Necessary code for button setup pinMode(BUTTON_FWD_PIN, INPUT_PULLUP); //Initialization of the Jewel @@ -187,56 +207,14 @@ void setup() { } void loop() { - boolean newState = digitalRead(BUTTON_FWD_PIN); - if((newState == LOW) && (oldState == HIGH)) { - delay(30); - newState = digitalRead(BUTTON_FWD_PIN); - if(newState == LOW) { - if(++mode > 4) mode = 0; - switch(mode) { - case 0: - // Nothing Taken - { NeoJewel.clear(); - NeoJewel.fill(pureRed, 0, 2); - NeoJewel.show(); - String morning_status = "false"; - String evening_status = "false"; - Serial.println(morning_status + evening_status); - break; } - case 1: - // Morning Taken - { NeoJewel.clear(); - NeoJewel.fill(pineGreen, 0, 1); - NeoJewel.fill(pureRed, 1, 2); - NeoJewel.show(); - String morning_status = "true"; - String evening_status = "false"; - Serial.println(morning_status + evening_status); - break; } - case 2: - // Evening Taken - { NeoJewel.clear(); - NeoJewel.fill(pineGreen, 1, 2); - NeoJewel.fill(pureRed, 0, 1); - NeoJewel.show(); - String morning_status = "false"; - String evening_status = "true"; - Serial.println(morning_status + evening_status); - break; } - case 3: - // Both Taken - { NeoJewel.clear(); - NeoJewel.fill(pineGreen, 0, 2); - NeoJewel.show(); - String morning_status = "true"; - String evening_status = "true"; - Serial.println(morning_status + evening_status); - break; } - } - } - } - // Set the last-read button state to the old state (reset) - oldState = newState; + buttonpush(); + client.loop(); + timeClient.update(); + + int hours = timeClient.getHours(); + int minutes = timeClient.getMinutes(); + int int_time = (hours*100)+minutes; + String current_time = String(int_time); if (WiFi.status() != WL_CONNECTED) { delay(1000); @@ -244,9 +222,77 @@ void loop() { ESP.restart(); } + if (!client.connected()) { + reconnect(); + } + + unsigned long currentMillis = millis(); + if (currentMillis - previousMillis >= interval) { + previousMillis = currentMillis; + Serial.print(int_time); + publishmqtt(current_time, morning_status, evening_status); + + // Reset to red colors and new payload at midnight + if (int_time == 0000) { + NeoJewel.clear(); + NeoJewel.fill(pureRed, 0, 2); + NeoJewel.show(); + morning_status = false; + evening_status = false; + } + } } -void publishmqtt(String current_time, String morning_status, String evening_status) { +void buttonpush() { + boolean newState = digitalRead(BUTTON_FWD_PIN); + if((newState == LOW) && (oldState == HIGH)) { + delay(20); + newState = digitalRead(BUTTON_FWD_PIN); + if(newState == LOW) { + if(++mode > 4) mode = 0; + switch(mode) { + case 1: + // Nothing Taken + { NeoJewel.clear(); + NeoJewel.fill(pureRed, 0, 2); + NeoJewel.show(); + morning_status = false; + evening_status = false; + break; } + case 2: + // Morning Taken + { NeoJewel.clear(); + NeoJewel.fill(pineGreen, 0, 1); + NeoJewel.fill(pureRed, 1, 2); + NeoJewel.show(); + morning_status = true; + evening_status = false; + break; } + case 3: + // Afternoon Taken + { NeoJewel.clear(); + NeoJewel.fill(pineGreen, 1, 2); + NeoJewel.fill(pureRed, 0, 1); + NeoJewel.show(); + morning_status = false; + evening_status = true; + break; } + case 4: + // Both Taken + { NeoJewel.clear(); + NeoJewel.fill(pineGreen, 0, 2); + NeoJewel.show(); + morning_status = true; + evening_status = true; + break; } + } + } + } + // Set the last-read button state to the old state (reset) + oldState = newState; +} + +void publishmqtt(String current_time, boolean morning_status, boolean evening_status) { StaticJsonDocument<200> doc; doc["time"] = (String)current_time; @@ -255,11 +301,9 @@ void publishmqtt(String current_time, String morning_status, String evening_stat serializeJsonPretty(doc, Serial); char data[200]; - Serial.println(data); - Serial.println(""); serializeJson(doc, data); client.publish(mqtt_topic, data, true); - delay(2000); + delay(50); yield(); } @@ -281,3 +325,17 @@ void reconnect() { } } ``` + +