Last active
March 1, 2025 02:24
-
-
Save tsohr/8bab3296d189683ffb229b5be346f76b to your computer and use it in GitHub Desktop.
a simple remote controller for home automation using esp8266
This file contains hidden or 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
#include <Arduino_JSON.h> | |
#include <Arduino.h> | |
#include "token.h" | |
#define STATE_HALF 1 | |
#define STATE_FULL 2 | |
#define STATE_OFF 0 | |
#define STATE_UNKNOWN -1 | |
int last_state = STATE_UNKNOWN; | |
long last_getStateStamp = 0; | |
const long debounce_getState = 30 * 1000; | |
long autooff_schedule = 0; | |
int conseq_error_count = 0; | |
// ----------------------------------------------------------- | |
#define ledPin 13 | |
#define buttonPin 14 | |
int buttonState; // the current reading from the input pin | |
int lastButtonState = LOW; // the previous reading from the input pin | |
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled | |
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers | |
void ledOn() { | |
digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level | |
} | |
void ledOff() { | |
digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH | |
} | |
void ledToggle() { | |
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Change the state of the LED | |
} | |
// ----------------------------------------------------------- | |
#include <ESP8266WiFi.h> | |
#include <ESP8266WiFiMulti.h> | |
#include <ESP8266HTTPClient.h> | |
#include <WiFiClientSecureBearSSL.h> | |
ESP8266WiFiMulti WiFiMulti; | |
void setupWifi() { | |
WiFi.mode(WIFI_STA); | |
WiFiMulti.addAP(STASSID, STAPSK); | |
Serial.println("setup() done connecting to ssid '" STASSID "'"); | |
} | |
bool clock_done = false; | |
void clock_sync() { | |
if (clock_done) { | |
return; | |
} | |
// Set time via NTP, as required for x.509 validation | |
configTime(3 * 3600, 0, "kr.pool.ntp.org", "time.nist.gov"); | |
Serial.print("Waiting for NTP time sync: "); | |
time_t now = time(nullptr); | |
while (now < 8 * 3600 * 2) { | |
delay(500); | |
Serial.print("."); | |
now = time(nullptr); | |
} | |
Serial.println(""); | |
struct tm timeinfo; | |
gmtime_r(&now, &timeinfo); | |
Serial.print("Current time: "); | |
Serial.print(asctime(&timeinfo)); | |
clock_done = true; | |
} | |
void setup() { | |
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output | |
pinMode(ledPin, OUTPUT); | |
pinMode(buttonPin, INPUT); | |
ledOn(); | |
analogWrite(ledPin, 128); | |
ledOn(); | |
Serial.begin(9600); | |
setupWifi(); | |
long now = millis(); | |
lastDebounceTime = now; | |
last_getStateStamp = now - debounce_getState; | |
} | |
int getState() { | |
long now = millis(); | |
if (now - last_getStateStamp < debounce_getState) { | |
return last_state; | |
} | |
last_getStateStamp = now; | |
std::unique_ptr<BearSSL::WiFiClientSecure> client(new BearSSL::WiFiClientSecure); | |
client->setInsecure(); | |
HTTPClient https; | |
String target_url = HA_URL; | |
target_url += "api/states/"; | |
target_url += TARGET; | |
Serial.printf("[HTTPS] begin... %s\n", target_url.c_str()); | |
if (! https.begin(*client, target_url.c_str())) { // HTTPS | |
Serial.printf("[HTTPS] Unable to connect\n"); | |
return last_state = STATE_UNKNOWN; | |
} | |
https.addHeader("Content-Type", "application/json"); | |
https.addHeader("Authorization", TOKEN); | |
Serial.print("[HTTPS] GET...\n"); | |
// start connection and send HTTP header | |
int httpCode = https.GET(); | |
// httpCode will be negative on error | |
if (httpCode <= 0) { | |
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); | |
https.end(); | |
conseq_error_count ++; | |
return last_state = STATE_UNKNOWN; | |
} | |
// HTTP header has been send and Server response header has been handled | |
Serial.printf("[HTTPS] GET... code: %d\n", httpCode); | |
if (! (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)) { | |
Serial.printf("[HTTPS] GET... failed, unexpected httpCode: %d\n", httpCode); | |
https.end(); | |
conseq_error_count ++; | |
return last_state = STATE_UNKNOWN; | |
} | |
// file found at server | |
String payload = https.getString(); | |
Serial.println(payload); | |
JSONVar myObject = JSON.parse(payload); | |
// JSON.typeof(jsonVar) can be used to get the type of the variable | |
if (JSON.typeof(myObject) == "undefined") { | |
Serial.println("Parsing input failed!"); | |
https.end(); | |
return last_state = STATE_UNKNOWN; | |
} | |
if (! myObject.hasOwnProperty("state")) { | |
Serial.println("no such property: state...???"); | |
https.end(); | |
return last_state = STATE_UNKNOWN; | |
} | |
const char* state = (const char*) myObject["state"]; | |
Serial.println(state); | |
if (strncasecmp(state, "off", 3) == 0) { | |
https.end(); | |
autooff_schedule = 0; // no job required. | |
return last_state = STATE_OFF; | |
} else if (strncasecmp(state, "on", 2) == 0) { | |
if (! myObject.hasOwnProperty("attributes")) { | |
Serial.println("no such property: attributes...???"); | |
https.end(); | |
return last_state = STATE_UNKNOWN; | |
} | |
JSONVar attributes = myObject["attributes"]; | |
if (! attributes.hasOwnProperty("percentage")) { | |
Serial.println("no such property: attributes.percentage...???"); | |
https.end(); | |
return last_state = STATE_UNKNOWN; | |
} | |
int percentage = (int) attributes["percentage"]; | |
Serial.printf("percentage: %d\n", percentage); | |
if (0 <= percentage && percentage < 55) { | |
https.end(); | |
return last_state = STATE_HALF; | |
} | |
if (percentage >= 52) { | |
https.end(); | |
return last_state = STATE_FULL; | |
} | |
https.end(); | |
return last_state = STATE_UNKNOWN; | |
} | |
return last_state = STATE_UNKNOWN; | |
} | |
bool setState(int state) { | |
std::unique_ptr<BearSSL::WiFiClientSecure> client(new BearSSL::WiFiClientSecure); | |
client->setInsecure(); | |
HTTPClient https; | |
String target_url; | |
if (state == STATE_HALF || state == STATE_FULL) { | |
target_url = HA_URL; | |
target_url += "api/services/fan/turn_on"; | |
} else { | |
target_url = HA_URL; | |
target_url += "api/services/fan/turn_off"; | |
} | |
Serial.printf("[HTTPS] begin... %s\n", target_url.c_str()); | |
if (! https.begin(*client, target_url.c_str())) { // HTTPS | |
Serial.printf("[HTTPS] Unable to connect\n"); | |
return false; | |
} | |
https.addHeader("Content-Type", "application/json"); | |
https.addHeader("Authorization", TOKEN); | |
JSONVar payload; | |
payload["entity_id"] = TARGET; | |
if (state == STATE_HALF || state == STATE_FULL) { | |
payload["percentage"] = state == STATE_HALF ? 52 : 100; | |
} | |
String payload_str = JSON.stringify(payload); | |
Serial.printf("[HTTPS] POST...%s\n", payload_str.c_str()); | |
// start connection and send HTTP header | |
int httpCode = https.POST(payload_str); | |
// httpCode will be negative on error | |
if (httpCode <= 0) { | |
Serial.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str()); | |
https.end(); | |
conseq_error_count ++; | |
return false; | |
} | |
// HTTP header has been send and Server response header has been handled | |
Serial.printf("[HTTPS] POST... code: %d\n", httpCode); | |
if (! (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)) { | |
Serial.printf("[HTTPS] POST... failed, unexpected httpCode: %d\n", httpCode); | |
https.end(); | |
conseq_error_count ++; | |
return false; | |
} | |
// file found at server | |
String response = https.getString(); | |
Serial.println(response); | |
https.end(); | |
long now = millis(); | |
last_getStateStamp = now; | |
last_state = state; | |
if (state == STATE_HALF) { | |
autooff_schedule = now + 30 * 60 * 1000; // 30min | |
} else if (state == STATE_FULL) { | |
autooff_schedule = now + 10 * 60 * 1000; // 10min | |
} else { | |
autooff_schedule = 0; | |
} | |
return true; // control OK. | |
} | |
void onButton(long now) { | |
Serial.println("button pressed."); | |
// last_getStateStamp = now - debounce_getState - 1; | |
int state = getState(); | |
if (state == STATE_HALF) { | |
setState(STATE_FULL); | |
} else if (state == STATE_FULL) { | |
setState(STATE_OFF); | |
} else { | |
setState(STATE_HALF); | |
} | |
} | |
void readButton() { | |
long now = millis(); | |
int reading = digitalRead(buttonPin); | |
if (reading != lastButtonState) { | |
lastDebounceTime = now; | |
} | |
if ((now - lastDebounceTime) > debounceDelay) { | |
if (reading != buttonState) { | |
buttonState = reading; | |
if (buttonState == HIGH) { | |
onButton(now); | |
} | |
} | |
} | |
// save the reading. Next time through the loop, it'll be the lastButtonState: | |
lastButtonState = reading; | |
} | |
int last_wifi_state = -1; | |
void loop() { | |
long now = millis(); | |
if (conseq_error_count > 10) { | |
ESP.reset(); | |
return; | |
} | |
int wifi_state = WiFiMulti.run(); | |
if (last_wifi_state != wifi_state) { | |
Serial.printf("wifi state: %d -> %d\n", last_wifi_state, wifi_state); | |
last_wifi_state = wifi_state; | |
} | |
if (wifi_state != WL_CONNECTED) { | |
ledToggle(); | |
return; | |
} | |
int fade_value = (int)(abs((now % 8000) - 4000) * 80 / 4000 + (255-80)); | |
analogWrite(LED_BUILTIN, fade_value); | |
clock_sync(); | |
readButton(); | |
if (autooff_schedule > 0 && now > autooff_schedule) { | |
if (setState(false)) { | |
digitalWrite(ledPin, 0); | |
} else { | |
analogWrite(ledPin, 128); | |
} | |
autooff_schedule = 0; | |
} | |
int state = getState(); | |
if (state == STATE_OFF) { | |
digitalWrite(ledPin, 0); | |
} else if (state == STATE_HALF) { | |
if (now % 1000 < 500) { | |
analogWrite(ledPin, 50); | |
} else { | |
analogWrite(ledPin, 20); | |
} | |
} else if (state == STATE_FULL) { | |
analogWrite(ledPin, 128); | |
} else { | |
if (now % 2000 < 1400) { | |
digitalWrite(ledPin, 0); | |
} else { | |
digitalWrite(ledPin, 1); | |
} | |
} | |
} |
This file contains hidden or 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
#ifndef __TOKEN__ | |
#define __TOKEN | |
const char* TOKEN = "Bearer blarblar"; | |
const char* TARGET = "fan.fan_switch_module"; | |
const char* HA_URL = "https://home_assistant/"; | |
#ifndef STASSID | |
#define STASSID "GuestNet" | |
#define STAPSK "12345678" | |
#endif | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment