Skip to content

Instantly share code, notes, and snippets.

@tsohr
Last active March 1, 2025 02:24
Show Gist options
  • Save tsohr/8bab3296d189683ffb229b5be346f76b to your computer and use it in GitHub Desktop.
Save tsohr/8bab3296d189683ffb229b5be346f76b to your computer and use it in GitHub Desktop.
a simple remote controller for home automation using esp8266
#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);
}
}
}
#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