Added the ESP Medicine post for a Monday morning release.
This commit is contained in:
@ -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
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
<div class="column">
|
||||
<img src="../posts/img/esp_indicator_1.png">
|
||||
<img src="../posts/img/esp_indicator_2.png">
|
||||
</div>
|
||||
<div class="column">
|
||||
<img src="../posts/img/esp_indicator_3.png">
|
||||
<img src="../posts/img/esp_indicator_wires.png">
|
||||
</div>
|
||||
|
||||
## Full Code
|
||||
|
||||
@ -107,7 +117,7 @@ Here is what I used to construct this:
|
||||
#include <secrets.h>
|
||||
|
||||
#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() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<style>
|
||||
.column {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
img {
|
||||
padding: 5px;
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user