commit 13f7706782b2aef1c6a2585948136ec4744fda75 Author: Anna Wiggins Date: Wed May 6 17:05:51 2020 -0400 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0bef08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.h +*.bin +*.elf diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d4e107 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +build: + arduino-cli compile -b esp8266:esp8266:huzzah smartswitch.ino diff --git a/config.h.example b/config.h.example new file mode 100644 index 0000000..9d533b5 --- /dev/null +++ b/config.h.example @@ -0,0 +1,21 @@ +#define WIFI_SSID "myNetwork" +#define WIFI_PASSWORD "myPassword" +#define WEBHOOK_URL "example.com/aoeuhtns" + +// In this example we have three pins that will +// be controlled: pin 5 will be a momentary switch +// and pins 8 and 10 will be sustained/latched switches. +// +// We would expect the webhook to return something like: +// +// 0 1 +// 1 0 +// 2 1 +// +// to activate the momentary switch, (pin 5) deactivate the first +// latched switch, (pin 8) and activate the second latched switch. (pin 10) +const int PIN_MAP[][2] = { + {5, 0}, + {8, 1}, + {10, 1} +}; diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..8ad514f --- /dev/null +++ b/readme.md @@ -0,0 +1,53 @@ +# Control a transistor with a webhook + +This is an Arduino IDE sketch for a "smart" controller that can activate pins +based on the state of some webpage. The motivating use cases are: + +* Controlling a PC power switch remotely, using a transistor wired to the power switch pins +* Lighting specific LEDs to create a remotely controlled 'traffic light'. + +This sketch currently targets only the ESP8266, and will probably not work with other +microcontrollers. Support for other boards may come if I run out of ESP8266's. + +## Configuration + +First: + +``` +cp config.h.example config.h +``` + +Then edit `config.h` and fill in the correct values for your environment. + +`PIN_MAP` in `config.h` is an array of pins that we want to control. Each item in the array is itself an +array, with the following format: + +``` +[output_pin, control_mode] +``` + +The index of the item in the top-level array is its 'index' value in the webhook. (see webhook data, below) + +`output_pin` is obviously the pin to control. + +`control_mode` is either 0 or 1. 0 is for momentary mode; that is, when the state is active the pin will +only be high for a short time. 1 is for latched mode; the pin will stay high until the state changes. + + + +## Building + +**TODO** + + +## Webhook data +The webhook should always return a page in the following format: + +``` +index_0 state_0 +index_1 state_1 +... +``` + +Where index and state are both integers. If you are expecting momentary input, you should return the +state to '0' after the page is served / the webhook is consumed. diff --git a/smartswitch.ino b/smartswitch.ino new file mode 100644 index 0000000..b4065e2 --- /dev/null +++ b/smartswitch.ino @@ -0,0 +1,142 @@ +#include "config.h" +#include +#include + +// how long to delay between each request to the +// server, in ms. +const int REQUEST_RATE = 1500; + +// how long to keep a momentary switch high. +// results approximate, and must be shorter than REQUEST_RATE. +const int MOMENTARY_PERIOD = 250; + +// Custom pin status for momentary switch that shouldn't +// get toggled again yet. +const int OLD_HIGH = 255; + +// Holds the current state of all the pins. +// Gets written to by parse_webhook_response(). +// Gets read from by set_pins(). +int pin_states[sizeof(PIN_MAP)] = {0}; + +// Just a static client object to avoid memory allocations. +HTTPClient client; + +void init_serial() { + Serial.begin(9600); +} + +void init_wifi() { + Serial.println("Attempting to (re)connect to wifi"); + WiFi.disconnect(); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + int elapsed = 0; + while (WiFi.status() != WL_CONNECTED) { + Serial.print("Wifi connecting for "); + Serial.print(elapsed); + Serial.println(" seconds"); + elapsed++; + delay(1000); + } + + Serial.println("Wifi connected"); +} + +void setup() { + init_serial(); + init_wifi(); +} + +void loop() { + poll_server(); + set_pins(); + int elapsed = handle_momentary(); + + Serial.flush(); + delay(REQUEST_RATE - elapsed); +} + +// if any momentary pins went high this loop, we +// wait the agreed upon delay and then turn them off. +// if any momentary data input went low, we "reset" +// the pin. +int handle_momentary() { + for (int i = 0; i < sizeof(pin_states); i++) { + // if [the pin is momentary] and [it was set high this cycle] + if (PIN_MAP[i][1] == 0 && pin_states[i] == HIGH) { + // wait for the specified amount of time, then set the pin + // back to low. + delay(MOMENTARY_PERIOD); + digitalWrite(PIN_MAP[i][0], LOW); + pin_states[i] = OLD_HIGH; + } + } +} + +// poll_server makes the actual HTTP request and handles +// the result. It returns false if an error occurred. +bool poll_server() { + client.begin(WEBHOOK_URL); + int status = client.GET(); + if (status < 0) { + Serial.print("Client error communicating with server: "); + Serial.println(status); + return false; + } + if (status <= 400) { + Serial.print("Received HTTP status code "); + Serial.println(status); + return false; + } + + parse_webhook_response(client.getString()); +} + +void parse_webhook_response(String raw_data) { + int data[sizeof(PIN_MAP)] = {-1}; + // TODO: split the data and turn it into numbers + + while (raw_data.length() > 0) { + // read to a newline + String line = raw_data.substring(0, raw_data.indexOf('\n')); + raw_data.remove(0, raw_data.indexOf('\n') + 1); + + // extract data from the line and add it to our incoming data array + int index = line.substring(0, raw_data.indexOf(' ')).toInt(); + int state = line.substring(raw_data.indexOf(' ')+1).toInt(); + data[index] = state; + } + + // we split this into a second loop so we can detect + // missing data more easily. + for (int i = 0; i < sizeof(data); i++) { + if (data[i] == -1) { + Serial.print("Did not receive data for pin "); + Serial.println(PIN_MAP[i][0]); + } + + if (tripped(i) && data[i] == HIGH) { + continue; + } + + pin_states[i] = data[i]; + } +} + +// Uses the current pin_states to actually write to the pins +void set_pins() { + for (int i = 0; i < sizeof(PIN_MAP); i++) { + if (tripped(i)) { + continue; + } + + digitalWrite(PIN_MAP[i][0], pin_states[i]); + } +} + +// returns true if pin at map index i (not pinout numbering) is momentary +// and has been tripped (received a 1 without a subsequent 0 yet) +// returns false otherwise +bool tripped(int i) { + return PIN_MAP[i][1] == 0 && pin_states[i] == OLD_HIGH; +}