Last active
January 9, 2019 09:31
-
-
Save laacz/22ac4472e228c162495bd6ddf04e0f4f to your computer and use it in GitHub Desktop.
Remote (ESP8266) and physical switching of lights, state saving, etc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This tiny ESP8266 sketch starts a web server, | |
* shows a button, allows to toggle one relay, | |
* saves its state to EEPROM, resotres upon reboot. | |
* Tries to connect to MQTT server to report on relay | |
* state changes via two topics. | |
* | |
* Sketch is used to play with simple multiway switches | |
* and ESP8266 + SPDT relay to be able to toggle lights | |
* from any of physical switches and also remotely. | |
* | |
* Also, it restores state upon restart, so there is | |
* are no accidentally switched on lights after electical | |
* outage in some cases. | |
* | |
* SPDT DPDT SPDT | |
* /+----+\ /+----+---+ | |
* ---+/ X /+----[LAMP] | |
* +----+/ \+----+---+/ | |
* | |
* Sketch is small, String's are here to stay. | |
* | |
* License: CC-BY | |
* Author: Kaspars Foigts | |
* Year: 2019 | |
*/ | |
#include <Arduino.h> | |
#include <ESP8266WiFi.h> | |
#include <ESP8266WebServer.h> | |
#include <EEPROM.h> | |
#include <PubSubClient.h> | |
// WiFi access params | |
#define WIFI_SSID "" | |
#define WIFI_PASS "" | |
// MQTT config | |
#define MQTT_HOST "" | |
#define MQTT_NAME "I-Relay" | |
// Standard for Wemos relay shield | |
#define RELAY_PIN D1 | |
// Default relay state | |
byte relay_state = HIGH; | |
ESP8266WebServer server(80); | |
WiFiClient espClient; | |
PubSubClient mqtt(espClient); | |
void connect_mqtt() | |
{ | |
Serial.print(String("Connecting to MQTT server at ") + MQTT_HOST + " "); | |
mqtt.setServer(MQTT_HOST, 1883); | |
if (!mqtt.connected()) | |
{ | |
mqtt.connect(String(String(MQTT_NAME) + String(random(0xffff), HEX)).c_str()); | |
delay(500); | |
Serial.print("."); | |
} | |
Serial.println(" Connected"); | |
} | |
/** | |
* Sets relay according to relay_state, writes to EEPROM. | |
*/ | |
void operateRelay() | |
{ | |
Serial.println(String("Switching relay ") + (relay_state == HIGH ? "on" : "off")); | |
// Toggles relay itself | |
digitalWrite(RELAY_PIN, relay_state); | |
// Saves state | |
EEPROM.write(0, relay_state); | |
EEPROM.commit(); | |
// Publish state to MQTT | |
String mqtt_topic; | |
String mqtt_payload; | |
if (mqtt.connected()) { | |
mqtt_topic = String("stat/") + MQTT_NAME + "/POWER"; | |
mqtt_payload = relay_state ? "ON" : "OFF"; | |
mqtt.publish(mqtt_topic.c_str(), mqtt_payload.c_str()); | |
Serial.println(mqtt_topic + ": " + mqtt_payload); | |
mqtt_topic = String("stat/") + MQTT_NAME + "/RESULT"; | |
mqtt_payload = String("{\"POWER\": \"") + (relay_state ? "ON" : "OFF") + "\"}"; | |
mqtt.publish(mqtt_topic.c_str(), mqtt_payload.c_str()); | |
Serial.println(mqtt_topic + ": " + mqtt_payload); | |
} | |
else | |
{ | |
Serial.println("Skipping MQTT publishing, not (yet) connected."); | |
} | |
} | |
/** | |
* Retrns HTML header. | |
*/ | |
String getHeader() | |
{ | |
return String("<!DOCTYPE html><html><head>" | |
"<meta charset='utf-8'>" | |
"<link rel='stylesheet' href='//cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css'>" | |
"<meta name='viewport' content='width=device-width, initial-scale=1'>" | |
"</head><body>" | |
"<section class='section has-text-centered'><div class='container'>" | |
"<h1 class='title'>Relay toggler</h1>"); | |
} | |
/** | |
* Retrns HTML footer. | |
*/ | |
String getFooter() | |
{ | |
return String("</div></section><footer class='footer'><p class='content has-text-centered'>") + | |
"Hello to you from " + server.client().remoteIP().toString() + | |
"<br>" | |
"Relay is on pin " + | |
RELAY_PIN + | |
"</p>" | |
"</footer><script>" | |
"document.addEventListener('DOMContentLoaded', () => {" | |
"document.getElementById('toggle').addEventListener('click', (evt) => {" | |
"evt.preventDefault();" | |
"fetch('/toggle', {" | |
"method: 'POST'," | |
"}).then((response) => response.json()).then((json) => {" | |
"document.getElementById('toggle').classList.toggle('is-primary', json.relay_state);" | |
"});" | |
"});" | |
"});" | |
"</script></body></html>"; | |
} | |
/** | |
* Handles GET / | |
*/ | |
void handleRoot() | |
{ | |
// Logs to serial | |
Serial.println("/ from " + server.client().remoteIP().toString()); | |
// Sends back HTML. | |
server.send(200, "text/html", getHeader() + "<p><button class='button " + (relay_state == 1 ? "is-primary" : "") + " is-large' id='toggle'>Poga</button></p>" + getFooter()); | |
} | |
/** | |
* Handles POST /toggle | |
*/ | |
void handleToggle() | |
{ | |
// Changes relay state variable opposite to current one | |
relay_state = relay_state == HIGH ? LOW : HIGH; | |
operateRelay(); | |
// Sends back state tp client as JSON | |
server.send(200, "application/json", String("{\"relay_state\": ") + relay_state + "}"); | |
} | |
void setup() | |
{ | |
Serial.begin(115200); | |
EEPROM.begin(sizeof(byte)); | |
// This will be the pin which toggles relay state | |
pinMode(RELAY_PIN, OUTPUT); | |
// Restores state | |
relay_state = EEPROM.read(0); | |
// Let's switch relay on or off according to our plan | |
operateRelay(); | |
// Connecting to designated WiFi | |
Serial.print(String("Connecting to ") + WIFI_SSID + " "); | |
WiFi.begin(WIFI_SSID, WIFI_PASS); | |
while (WiFi.status() != WL_CONNECTED) | |
{ | |
delay(500); | |
Serial.print("."); | |
} | |
Serial.println(" connected."); | |
Serial.print("IP address: "); | |
Serial.println(WiFi.localIP()); | |
// Add callbacks on various URIs | |
server.on("/", HTTP_GET, handleRoot); | |
server.on("/toggle", HTTP_POST, handleToggle); | |
server.begin(); | |
Serial.println("HTTP server started"); | |
} | |
void loop() | |
{ | |
// Check if we have a request from anyone often | |
server.handleClient(); | |
// If MQTT is not connected, connect | |
if (!mqtt.connected()) | |
{ | |
connect_mqtt(); | |
} | |
mqtt.loop(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment