Skip to content

Instantly share code, notes, and snippets.

@philharlow
Created March 1, 2021 05:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philharlow/6397fe8275e9bda25dcbeb37e290911f to your computer and use it in GitHub Desktop.
Save philharlow/6397fe8275e9bda25dcbeb37e290911f to your computer and use it in GitHub Desktop.
#include <ESP8266WiFi.h>
#include <DNSServer.h> //Local DNS Server used for redirecting all requests to the configuration portal
#include <ESP8266WebServer.h> //Local WebServer used to serve the configuration portal
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
#include <WebSocketsServer.h>
#include <Encoder.h>
#include <EEPROM.h>
#include <Espalexa.h>
#include <ESP8266HTTPUpdateServer.h>
#define enablePin D8
#define dir1Pin D4
#define dir2Pin D5
// Update me!
#define upButtonPin D7
#define downButtonPin D6
#define ledPin D3
Encoder myEnc(D1, D2);
Espalexa espalexa;
EspalexaDevice* blindsDevice = NULL;
WebSocketsServer webSocket = WebSocketsServer(81); // create a websocket server on port 81
ESP8266HTTPUpdateServer* httpUpdater;
bool motorEnabled = false;
int targetPosition = 0;
int disableMotorIn = 0;
int saveSettingsIn = 0;
int startingPositionOffset = 0;
int singleButtonHeldFor = 0;
int quickUpDownThreshold = 250;
int encoderPos = 0;
int lastEncoderPos = encoderPos;
int stallThreshold = 1000;
bool ledOn = true;
int changeLedIn = 20;
bool inProgrammingMode = false;
int programmingHeldFor = 0;
int unsetPosition = -100000;
int position1 = unsetPosition;
int position2 = unsetPosition;
int stalledTicks = 0;
// Change this before you flash
const char* ssid = "***";
const char* password = "***";
boolean wifiConnected = false;
boolean connectWifi();
void brightnessChanged(uint8_t brightness);
#define INITED_VALUE 225 // changing will nuke all saved settings
struct SettingsStruct
{
int inited;
int openPosition;
int currentPosition;
char name[30];
};
SettingsStruct defaultSettings =
{
INITED_VALUE,
200,
0,
"Blinds"
};
SettingsStruct settings = defaultSettings;
float getPercentageOpen()
{
return clamp(settings.currentPosition, 0, settings.openPosition) / (float)settings.openPosition;
}
void saveSettings()
{
if (inProgrammingMode)
return;
EEPROM.begin(sizeof(settings));
EEPROM.put(0, settings);
EEPROM.end();
Serial.println("Saved settings!");
int value = getPercentageOpen() * 255.f;
if (blindsDevice)
blindsDevice->setValue(value);
sendToSocket("saved");
}
void loadSettings()
{
SettingsStruct newSettings;
EEPROM.begin(sizeof(newSettings));
EEPROM.get(0, newSettings);
EEPROM.end();
//Serial.print("Loaded struct inited: ");
//Serial.println(newSettings.inited);
if (newSettings.inited == INITED_VALUE)
{
settings = newSettings;
Serial.print("Loaded settings openPosition: ");
Serial.print(settings.openPosition);
Serial.print(" currentPosition: ");
Serial.println(settings.currentPosition);
}
else
{
Serial.println("Initial settings save");
saveSettings();
}
// Dont move on startup
startingPositionOffset = settings.currentPosition;
targetPosition = settings.currentPosition;
}
int tickCounter = 0;
void updateLED()
{
//digitalWrite(ledPin, ledOn ? HIGH : LOW);
analogWrite(ledPin, ledOn ? 100 : 0);
if (changeLedIn > 0)
changeLedIn--;
if (changeLedIn == 0)
{
if (wifiConnected == false)
{
ledOn = !ledOn;
changeLedIn = 200;
}
else if (inProgrammingMode)
{
ledOn = !ledOn;
changeLedIn = 100;
}
else
ledOn = motorEnabled;
}
}
void setup()
{
Serial.begin(115200);
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, LOW);
pinMode(dir1Pin, OUTPUT);
pinMode(dir2Pin, OUTPUT);
pinMode(upButtonPin, INPUT_PULLUP);
pinMode(downButtonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
updateLED();
delay(100);
Serial.println();
bool upButtonPressed = digitalRead(upButtonPin) == LOW;
bool downButtonPressed = digitalRead(downButtonPin) == LOW;
if (upButtonPressed && downButtonPressed)
saveSettings();
else
loadSettings();
WiFiManager wifiManager;
//wifiManager.resetSettings();
String apName = "BlindsControllerSetup" + WiFi.macAddress().substring(11);
int str_len = apName.length() + 1;
char char_array[str_len];
apName.toCharArray(char_array, str_len);
if (!wifiManager.autoConnect(char_array)) {
Serial.println("Failed to connect, we should reset as see if it connects");
delay(3000);
ESP.reset();
delay(5000);
return;
}
// Initialise wifi connection
wifiConnected = true;// connectWifi();
if (wifiConnected)
{
String blindsName = "Blinds" + WiFi.macAddress().substring(11);
int startingValue = getPercentageOpen() * 255;
Serial.println("Device name: " + blindsName);
Serial.print("Setting the device's value to: ");
Serial.println(startingValue);
blindsDevice = new EspalexaDevice(blindsName, brightnessChanged, startingValue);
espalexa.addDevice(blindsDevice);
// blindsDevice->setValue(startingValue);
espalexa.begin();
httpUpdater = new ESP8266HTTPUpdateServer();
httpUpdater->setup(espalexa.server);
espalexa.server->on("/", HTTP_GET, handleRoot);
espalexa.server->on("/on", HTTP_GET, handleOnRequest);
espalexa.server->on("/open", HTTP_GET, handleOnRequest);
espalexa.server->on("/off", HTTP_GET, handleOffRequest);
espalexa.server->on("/close", HTTP_GET, handleOffRequest);
webSocket.begin(); // start the websocket server
webSocket.onEvent(webSocketEvent); // if there's an incomming websocket message, go to function 'webSocketEvent'
Serial.println("WebSocket server started.");
}
}
String getSpan(String name, String value)
{
String html = name + ": <span id='" + name + "'>" + value + "</span>";
return html;
}
void sendPage(bool redirect = false)
{
String html = "<meta name='viewport' content='user-scalable=no,width=device-width' />\r";
html += "<script>var connection = new WebSocket('ws://' + location.hostname + ':81/', ['arduino']);\r";
html += "connection.onopen = function() { console.log('connected socket!'); };\r";
html += "connection.onmessage = function(e)\r"
"{\r"
" var changes = e.data.split('/');\r"
" for (var i=0; i< changes.length; i++)\r"
" {\r"
" var vals = changes[i].split('=');\r"
" var div = document.getElementById(vals[0]);\r"
" if (div) div.innerHTML = vals[1];\r"
" }"
" console.log('Server: ', e.data);\r"
" sendIt('Rcvd');\r"
"};\r";
html += "connection.onerror = function(error) { console.log('WebSocket Error ', error); };\r";
html += "connection.onclose = function() { console.log('WebSocket connection closed'); };\r";
//html += "function intToHex(val) { var str = val.toString(16); if (str.length == 1) str = '0'+str; return str; } ";
//html += "function sendColor() { var str = intToHex(document.mainForm.red.value) + intToHex(document.mainForm.blue.value) + intToHex(document.mainForm.green.value) + intToHex(document.mainForm.white.value); connection.send('rgbw:' + str); } ";
html += "function sendIt(text) { connection.send(text); } \r</script> ";
String res = html;
res += "Hello from BlindsController v2!<br><br>\r\n\r\n";
res += "<a onclick='sendIt(\"on\");'>Open</a><br>\r\n";
res += "<a onclick='sendIt(\"off\");'>Close</a><br><br><br>\r\n";
res += "blindsDevice->Value: " + String(blindsDevice->getValue()) + "<br>\r\n";
res += getSpan("targetPosition", String(targetPosition)) + "<br>\r\n";
res += getSpan("currentPosition", String(settings.currentPosition)) + "<br>\r\n";
res += getSpan("openPosition", String(settings.openPosition)) + "<br>\r\n";
res += getSpan("startingPositionOffset", String(startingPositionOffset)) + "<br>\r\n";
res += getSpan("motorEnabled", String(motorEnabled)) + "<br>\r\n";
res += "\r\nFree Heap: " + (String)ESP.getFreeHeap();
res += "\r\nUptime: " + (String)millis();
espalexa.server->sendHeader("Location", String("/"), true);
espalexa.server->send(redirect ? 302 : 200, "text/html", res);
}
void handleRoot()
{
sendPage(false);
}
void handleOnRequest()
{
setOpenPercentage(1);
sendPage(true);
}
void handleOffRequest()
{
setOpenPercentage(0);
sendPage(true);
}
bool waiting = false;
String pending = "";
/*void sendPosition()
{
String msg = "currentPosition:";
msg += settings.currentPosition;
sendToSocket(msg);
}*/
void sendAll()
{
String msg = "currentPosition=";
msg += settings.currentPosition;
msg += "/targetPosition=";
msg += targetPosition;
msg += "/openPosition=";
msg += settings.openPosition;
msg += "/startingPositionOffset=";
msg += startingPositionOffset;
msg += "/motorEnabled=";
msg += motorEnabled;
sendToSocket(msg);
}
void sendToSocket(String msg)
{
if (waiting)
{
pending = msg;
}
else
{
waiting = true;
webSocket.sendTXT(0, msg);
}
}
bool send = false;
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) { // When a WebSocket message is received
switch (type) {
case WStype_DISCONNECTED: // if the websocket is disconnected
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED: { // if a new websocket connection is established
IPAddress ip = webSocket.remoteIP(num);
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
}
break;
case WStype_TEXT: // if new text data is received
Serial.printf("[%u] get Text: %s\n", num, payload);
String payload_str = String((char*)payload);
if (payload_str == "on")
{
setOpenPercentage(1);
send = true;
}
if (payload_str == "off")
{
setOpenPercentage(0);
send = true;
}
//int val = payload_str.toInt();
//if (isnan(val) == false)
// setOpenPercentage(val / 255.f);
waiting = false;
}
}
int clamp(int input, int minV, int maxV)
{
return max(minV, min(maxV, input));
}
void setTargetPosition(int position)
{
//Serial.print("Trying to goto: ");
//Serial.print(position);
//Serial.print(" current: ");
//Serial.println(settings.currentPosition);
if (inProgrammingMode == false)
position = clamp(position, 0, settings.openPosition);
if (settings.currentPosition == position)
return;
targetPosition = position;
motorEnabled = true;
if (inProgrammingMode == false)
ledOn = true;
}
void loop()
{
webSocket.loop();
updateLED();
if (send && waiting == false)
{
if (pending.length() > 0)
webSocket.sendTXT(0, pending);
else if (send)
sendAll();
pending = "";
send = false;
}
encoderPos = -myEnc.read();
settings.currentPosition = encoderPos + startingPositionOffset;
if (abs(encoderPos - lastEncoderPos) > 2)
{
Serial.print("encoder: ");
Serial.println(encoderPos);
lastEncoderPos = encoderPos;
stalledTicks = 0;
}
else if (motorEnabled)
{
stalledTicks++;
if (stalledTicks > stallThreshold)
{
Serial.println("Stall detected!!");
sendToSocket("stall");
motorEnabled = false;
ledOn = false;
}
}
bool upButtonPressed = digitalRead(upButtonPin) == LOW;
bool downButtonPressed = digitalRead(downButtonPin) == LOW;
int pressedCount = upButtonPressed + downButtonPressed;
if (pressedCount == 1)
singleButtonHeldFor++;
else
singleButtonHeldFor = 0;
if (upButtonPressed && downButtonPressed)
{
setTargetPosition(settings.currentPosition);
int threshold = inProgrammingMode ? 10 : 3000;
if (programmingHeldFor > threshold)
{
changeLedIn = 10;
programmingHeldFor = -1;
if (inProgrammingMode)
{
// save first pos
if (position1 == unsetPosition)
{
Serial.print("Setting position 1 to: ");
Serial.println(settings.currentPosition);
position1 = encoderPos;// settings.currentPosition;
ledOn = true;
changeLedIn = 2000;
}
else // second pos
{
Serial.print("Setting position 2 to: ");
Serial.println(settings.currentPosition);
position2 = encoderPos;// settings.currentPosition;
// settings.currentPosition = encoderPos + startingPositionOffset;
// now closed, pos1 = top/open
if (position1 > position2)
{
settings.openPosition = position1 - position2;
settings.currentPosition = 0;
}
// now open, pos2 = top/open
else
{
settings.openPosition = position2 - position1;
settings.currentPosition = settings.openPosition;
}
startingPositionOffset = -encoderPos + settings.currentPosition;
targetPosition = settings.currentPosition;
Serial.print("Saving new settings! openPosition: ");
Serial.print(settings.openPosition);
inProgrammingMode = false;
saveSettings();
ledOn = true;
changeLedIn = 10;
}
}
else
{
Serial.println("Entering programming mode");
position1 = unsetPosition;
position2 = unsetPosition;
inProgrammingMode = true;
changeLedIn = 1;
}
}
else if (programmingHeldFor > -1)
programmingHeldFor++;
}
else
{
programmingHeldFor = 0;
if (stalledTicks > stallThreshold)
{
if (upButtonPressed == false && downButtonPressed == false)
stalledTicks = 0;
}
else
{
if (inProgrammingMode)
{
if (upButtonPressed)
setTargetPosition(settings.currentPosition + 1);
else if (downButtonPressed)
setTargetPosition(settings.currentPosition - 1);
}
else
{
// prevent judder, override wwhen in programming mode
if (upButtonPressed && settings.currentPosition < settings.openPosition)
setTargetPosition(singleButtonHeldFor > quickUpDownThreshold ? settings.currentPosition + 1 : settings.openPosition);
else if (downButtonPressed && settings.currentPosition > 0)
setTargetPosition(singleButtonHeldFor > quickUpDownThreshold ? settings.currentPosition - 1 : 0);
}
}
}
if (motorEnabled)
{
bool goingUp = targetPosition > settings.currentPosition;
digitalWrite(enablePin, HIGH);
digitalWrite(dir1Pin, goingUp ? LOW : HIGH);
digitalWrite(dir2Pin, goingUp ? HIGH : LOW);
saveSettingsIn = 5000;
disableMotorIn = 2000;
if ((goingUp && settings.currentPosition >= targetPosition) ||
(goingUp == false && settings.currentPosition <= targetPosition))
{
motorEnabled = false;
if (inProgrammingMode == false)
{
int value = getPercentageOpen() * 255.f;
blindsDevice->setValue(value);
ledOn = false;
}
}
sendAll();
}
else
{
digitalWrite(dir1Pin, LOW);
digitalWrite(dir2Pin, LOW);
if (disableMotorIn > 0)
{
disableMotorIn--;
if (disableMotorIn == 0)
digitalWrite(enablePin, LOW);
}
}
if (saveSettingsIn > 0)
{
saveSettingsIn--;
if (saveSettingsIn == 0)
saveSettings();
}
if (wifiConnected)
{
espalexa.loop();
delay(1);
}
}
// connect to wifi � returns true if successful or false if not
boolean connectWifi() {
boolean state = true;
int i = 0;
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting ...");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (i > 10) {
state = false;
break;
}
i++;
}
if (state) {
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
else {
Serial.println("");
Serial.println("Connection failed.");
}
return state;
}
//our callback functions
void brightnessChanged(uint8_t brightness)
{
Serial.print("brightnessChanged ");
Serial.println(brightness);
if (inProgrammingMode)
{
Serial.println("Cannot set positin via alexa during programming!");
return;
}
// this seems redundant
blindsDevice->setValue(brightness);
float percentage = brightness / 255.f;
setOpenPercentage(percentage);
}
void setOpenPercentage(float percentage)
{
int newTarget = percentage * (float)settings.openPosition;
newTarget = clamp(newTarget, 0, settings.openPosition);
if (settings.currentPosition < 0 && newTarget == 0)
return;
if (settings.currentPosition > settings.openPosition && newTarget == settings.openPosition)
return;
setTargetPosition(newTarget);
Serial.print("newTarget: ");
Serial.println(newTarget);
Serial.print("Device 1 changed to ");
if (percentage > 0.f) {
Serial.print("ON, brightness: ");
Serial.println(percentage);
}
else {
Serial.println("OFF");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment