Initial commit.
This commit is contained in:
commit
13f7706782
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
config.h
|
||||||
|
*.bin
|
||||||
|
*.elf
|
2
Makefile
Normal file
2
Makefile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
build:
|
||||||
|
arduino-cli compile -b esp8266:esp8266:huzzah smartswitch.ino
|
21
config.h.example
Normal file
21
config.h.example
Normal file
|
@ -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}
|
||||||
|
};
|
53
readme.md
Normal file
53
readme.md
Normal file
|
@ -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.
|
142
smartswitch.ino
Normal file
142
smartswitch.ino
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
#include "config.h"
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESP8266HTTPClient.h>
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user