Skip to content

Instantly share code, notes, and snippets.

@tlrobinson
Last active August 29, 2015 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tlrobinson/8727476 to your computer and use it in GitHub Desktop.
Save tlrobinson/8727476 to your computer and use it in GitHub Desktop.
Arduino Yún version of @jwz's Arduino curtain project (http://www.jwz.org/curtain/)
#!/bin/sh
HOST="arduino.local"
USER="root"
PASSWORD="arduino"
COMMAND="$1"
curl -u "$USER:$PASSWORD" "http://$HOST/arduino/$COMMAND"
/* -*- Mode: C -*-
Arduino code for jwz's curtain controller
Copyright © 2011-2012 Jamie Zawinski <jwz@jwz.org>
Permission to use, copy, modify, distribute, and sell this software andTOGG its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation. No representations are made about the suitability of this
software for any purpose. It is provided "as is" without express or
implied warranty.
See http://www.jwz.org/curtain/ for details.
The combination of an Arduino Ethernet and a Relay Shield is used to
turn a motor on and off, forward or backward, to open and close curtains.
The device has a switch on it that adjustable stops on the motor's wheel
hit to tell us when we've reached the end. (The wheel flips the switch
to the left at one end, and to the right at the other.)
The device also has a momentary push-button on it, which behaves like
the TOGGLE command.
It reads commands from either the serial port or the ethernet connection.
All of these commands print "OK" once they have started operating:
OPEN -- Open the curtain if it's not open.
CLOSE -- Close the curtain if it's open.
STOP -- Turn off the motor right now.
TOGGLE -- If running, stop. If not running, open or close.
Different responses:
QUERY -- What is the current state? Prints one of:
OPEN, CLOSED, OPENING, CLOSING.
HELP -- Lists commands.
Unknown commands print "ERROR".
Output lines beginning with "-" are not command responses, but are
asynchronous status messages, useful for debugging.
Created: 26-Aug-2011
*/
//#include <SPI.h>
//#include <Ethernet.h>
#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>
// Listen on default port 5555, the webserver on the Yun
// will forward there all the HTTP requests for us.
YunServer server;
//#define DO_DHCP
// SERIOUSLY?? I am expected to *type in the number printed on the board*?
//
//byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xE3, 0xCB };
//
//#ifndef DO_DHCP
// byte ip[] = { 10, 0, 1, 9 }; // An older, related indignity.
//#endif
//
//
//int listen_port = 10001;
// Serial RX 0
// Serial TX 1
// Interrupt 2
// Interrupt 3
#define RELAY1_PIN 4 // Hard-coded pins for relays on the Relay Shield.
#define RELAY2_PIN 5
#define RELAY3_PIN 6
#define RELAY4_PIN 7
#define MANUAL_PIN 8 // User-accessible momentary switch (TOGGLE command)
#define DETECT_PIN 9 // Whether detect switch is right (curtain closed)
// Ethernet 10 - 13
#define RELAY_RUNNING1_PIN RELAY1_PIN // Relays 1 and 2 are power:
#define RELAY_RUNNING2_PIN RELAY2_PIN // High for motor running.
#define RELAY_DIRECTION1_PIN RELAY3_PIN // Relays 3 and 4 are direction:
#define RELAY_DIRECTION2_PIN RELAY4_PIN // High for closing, low for opening.
#define CMD_UNKNOWN 100
#define CMD_OPEN 101
#define CMD_CLOSE 102
#define CMD_STOP 103
#define CMD_TOGGLE 104
#define CMD_TOGGLE2 105
#define CMD_QUERY 106
#define CMD_HELP 107
// When changing the direction of the motor, we have to wait this long
// to avoid weirdness.
//
#define MOTOR_HYSTERESIS 1000
// Ignore state changes in momentary switches that happen too fast.
//
#define DEBOUNCE_HYSTERESIS 50
// We have to wait this long after switching a relay to be able to count
// on it having actually switched.
//
#define RELAY_LAG 50
// If the motor has been running for this many seconds, emergency stop.
// That must mean that something has gone wrong, so let's not burn the
// thing out.
//
#define MAX_RUNTIME 60
//
//EthernetServer server = 0;
//EthernetClient client = 0;
//bool server_initted = false; // Whether ethernet is up and listening.
//bool client_initted = false; // Whether a client is connected.
char cmd_buffer[20];
// Welcome to the New Stone Age.
#define TOLOWER(C) (((C) >= 'A' && (C) <= 'Z') ? (C)-('A'-'a') : (C))
int
strncasecmp (const char *s1, const char *s2, int n)
{
unsigned char c1, c2;
if (s1 == s2 || n == 0) return 0;
do {
c1 = TOLOWER (*s1);
c2 = TOLOWER (*s2);
if ((--n == 0) || c1 == 0)
break;
++s1;
++s2;
} while (c1 == c2);
return c1 - c2;
}
char *
memmove (char *dest0, char const *source0, unsigned int length)
{
char *dest = dest0;
char const *source = source0;
if (source < dest)
for (source += length, dest += length; length; --length)
*--dest = *--source;
else if (source != dest)
for (; length; --length)
*dest++ = *source++;
return dest0;
}
// Push a char onto the cmd_buffer.
//
void buffer_char (char c)
{
if (c <= 0) return;
char *s;
int i;
for (i = 0, s = cmd_buffer;
i < sizeof(cmd_buffer);
i++, s++)
if (!*s) break;
if (i < sizeof(cmd_buffer)-1) {
*s++ = c;
*s = 0;
} else if (c == '\n' || c == '\r') {
*cmd_buffer = 0; // EOL, flush buffer.
}
}
// Returns a CMD_ constant, or 0.
//
int get_cmd (void)
{
// Read from both serial and ethernet. Interleave characters as they
// arrive into the same buffer, because, who cares.
while (Serial.available() > 0) {
buffer_char (Serial.read());
}
// if (server_initted) {
// client = server.available();
// if (client) {
// client_initted = true;
// while (client.available()) {
// buffer_char (client.read());
// }
// } else {
// client_initted = false;
// }
// }
// If the cmd_buffer has CR or LF in it, parse the first line and
// remove it from the buffer.
//
char *s;
for (s = cmd_buffer; *s; s++) {
int cmd;
if (*s == '\r' || *s == '\n') {
int L = s - cmd_buffer;
if (L == 0)
cmd = 0;
else if (L == 4 && !strncasecmp (cmd_buffer, "OPEN", L))
cmd = CMD_OPEN;
else if (L == 5 && !strncasecmp (cmd_buffer, "CLOSE", L))
cmd = CMD_CLOSE;
else if (L == 4 && !strncasecmp (cmd_buffer, "STOP", L))
cmd = CMD_STOP;
else if (L == 6 && !strncasecmp (cmd_buffer, "TOGGLE", L))
cmd = CMD_TOGGLE;
else if (L == 5 && !strncasecmp (cmd_buffer, "QUERY", L))
cmd = CMD_QUERY;
else if (L == 4 && !strncasecmp (cmd_buffer, "HELP", L))
cmd = CMD_HELP;
else
cmd = CMD_UNKNOWN;
memmove ((char *) cmd_buffer, s+1, sizeof(cmd_buffer)-L-1);
cmd_buffer[sizeof(cmd_buffer)-1] = 0;
return cmd;
}
}
return 0;
}
void cmd_reply1 (const char *s)
{
Serial.print (s);
// if (client_initted) client.print (s);
}
void cmd_reply (const char *s)
{
cmd_reply1 (s);
Serial.println ("");
Serial.flush ();
// if (client_initted) client.println ("");
}
int last_cmd_pin_state = 0; // The momentary switch on MANUAL_PIN.
unsigned long last_cmd_pin_time = 0; // When it last changed state.
int button_state = 0; // The de-bounced version.
int last_open_p = 0; // The position-detect switch on the wheel.
int desired_running = 0; // Whether motor should be spinning.
int desired_opening = 0; // Whether motor should be +12v or -12v.
int is_running = 0; // Whether motor is spinning.
int is_opening = 0; // Whether motor is +12v or -12v, if spinning.
unsigned long last_on_time = 0; // When we last powered on the motor.
unsigned long last_off_time = 0; // When we last powered off the motor.
void setup (void)
{
// Bridge startup
pinMode(13, OUTPUT);
digitalWrite(13, LOW);
delay(500);
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
Bridge.begin();
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
digitalWrite(13, HIGH);
// Listen for incoming connection only from localhost
// (no one from the external network could connect)
server.listenOnLocalhost();
server.begin();
Serial.begin (9600);
cmd_reply ("-Hi! I'm the curtain! Hi!");
//# ifdef DO_DHCP
// cmd_reply ("-Requesting DHCP");
// if (Ethernet.begin (mac) == 0) {
// cmd_reply ("-DHCP failed");
// }
// else
//# else /* ! DO_DHCP */
// cmd_reply ("-Using static IP");
// Ethernet.begin (mac, ip);
//# endif /! DO_DHCP */
// {
// Serial.print ("-IP = ");
// for (byte b = 0; b < 4; b++) {
// if (b != 0) Serial.print (".");
// Serial.print (Ethernet.localIP()[b], DEC);
// }
// Serial.println ("");
//
// server = EthernetServer (listen_port);
// server.begin();
// server_initted = true;
// }
pinMode (RELAY1_PIN, OUTPUT);
pinMode (RELAY2_PIN, OUTPUT);
pinMode (RELAY3_PIN, OUTPUT);
pinMode (RELAY4_PIN, OUTPUT);
pinMode (DETECT_PIN, INPUT);
pinMode (MANUAL_PIN, INPUT);
digitalWrite (RELAY1_PIN, LOW);
digitalWrite (RELAY2_PIN, LOW);
digitalWrite (RELAY3_PIN, LOW);
digitalWrite (RELAY4_PIN, LOW);
last_open_p = -1;
int i;
for (i = 0; i < sizeof(cmd_buffer); i++)
cmd_buffer[i] = 0;
}
static int ostate = 0;
int process(YunClient client) {
// read the command
String command = client.readStringUntil('\r');
if (command == "open") {
client.println("OK");
return CMD_OPEN;
}
if (command == "close") {
client.println("OK");
return CMD_CLOSE;
}
if (command == "toggle") {
client.println("OK");
return CMD_TOGGLE;
}
if (command == "stop") {
client.println("OK");
return CMD_TOGGLE;
}
if (command == "query") {
int open_p = !digitalRead (DETECT_PIN);
if (open_p && !is_running)
client.println ("OPEN");
else if (!open_p && !is_running)
client.println ("CLOSED");
else if (open_p)
client.println ("CLOSING");
else
client.println ("OPENING");
return 0;
}
client.println("NOT FOUND");
return 0;
}
void loop (void)
{
unsigned long now = millis();
int cmd = 0;
// If the momentary switch is pressed and released, interpret it
// as CMD_TOGGLE. But de-bounce it on both ends first.
//
{
int cmd_pin_state = 1;//digitalRead (MANUAL_PIN);
if (cmd_pin_state != last_cmd_pin_state) {
last_cmd_pin_time = now;
}
if (last_cmd_pin_time > now) last_cmd_pin_time = 0; // time wrapped
// Only believe the button state if it has stayed that way for a while.
//
if (last_cmd_pin_time + DEBOUNCE_HYSTERESIS < now) {
if (button_state && !cmd_pin_state) {
cmd = CMD_TOGGLE2; // Gone from "pressed" to "not pressed", debounced.
}
button_state = cmd_pin_state;
}
last_cmd_pin_state = cmd_pin_state;
}
// Get clients coming from server
YunClient client = server.accept();
// There is a new client?
if (client) {
// Process request
cmd = process(client);
// Close connection and free resources.
client.stop();
}
int open_p = !digitalRead (DETECT_PIN);
if (open_p != last_open_p)
cmd_reply (open_p ? "-Switch open" : "-Switch closed");
last_open_p = open_p;
// If we don't have a command already from the momentary switch,
// read a textual command from ethernet or the serial port.
if (! cmd)
cmd = get_cmd();
switch (cmd) {
case 0:
break;
case CMD_OPEN:
desired_running = 1;
desired_opening = 1;
cmd_reply ("OK");
break;
case CMD_CLOSE:
desired_running = 1;
desired_opening = 0;
cmd_reply ("OK");
break;
case CMD_STOP:
desired_running = 0;
cmd_reply ("OK");
break;
case CMD_TOGGLE:
case CMD_TOGGLE2:
if (is_running) { // running -> stop
desired_running = 0;
} else if (open_p) { // open -> closed
desired_running = 1;
desired_opening = 0;
} else { // closed -> open
desired_running = 1;
desired_opening = 1;
}
cmd_reply (cmd == CMD_TOGGLE2 ? "-Button pressed" : "OK");
break;
case CMD_QUERY:
if (open_p && !is_running)
cmd_reply ("OPEN");
else if (!open_p && !is_running)
cmd_reply ("CLOSED");
else if (open_p)
cmd_reply ("CLOSING");
else
cmd_reply ("OPENING");
break;
case CMD_HELP:
cmd_reply ("OPEN, CLOSE, STOP, TOGGLE, QUERY, HELP");
break;
default:
cmd_reply ("ERROR");
break;
}
if (last_on_time > now) last_on_time = 0; // time wrapped
if (last_off_time > now) last_off_time = 0;
// If the motor has been on for an unreasonably long time,
// assume there's a physical problem, and shut it off.
//
if (is_running && (now - last_on_time) > (MAX_RUNTIME * 1000)) {
cmd_reply ("-Running too long! Emergency stop!");
desired_running = 0;
}
if (desired_running) {
if (desired_opening && open_p) {
cmd_reply ("-Done opening");
desired_running = 0; // done opening
} else if (!desired_opening && !open_p) {
cmd_reply ("-Done closing");
desired_running = 0; // done closing
}
}
if (is_running && !desired_running) {
// Request has been made to turn off the motor, when it was on.
// Do so immediately.
last_off_time = now;
is_running = 0;
cmd_reply ("-Stopping now");
} else if (!is_running && desired_running) {
// Request has been made to turn on the motor, when it was off.
// Do so only if it has been off for long enough.
if (last_off_time + MOTOR_HYSTERESIS < now) {
last_on_time = now;
is_running = 1;
is_opening = desired_opening;
cmd_reply ("-Starting now");
} else {
cmd_reply1 ("-Starting momentarily");
}
} else if (is_running && desired_opening != is_opening) {
// Request has been made to reverse the direction of the motor while on.
// Turn it off immediately, then wait. The next time through the loop,
// this will look like "request to turn motor on".
last_off_time = now;
is_running = 0;
cmd_reply ("-Stopping now, reversing momentarily");
} else {
if (is_running && !desired_running)
last_off_time = now;
else if (!is_running && desired_running)
last_on_time = now;
is_running = desired_running;
is_opening = desired_opening;
}
// Flush is_running and is_opening down into the hardware.
if (!is_running) {
digitalWrite (RELAY_RUNNING1_PIN, LOW);
digitalWrite (RELAY_RUNNING2_PIN, LOW);
} else if (is_opening) {
digitalWrite (RELAY_DIRECTION1_PIN, LOW);
digitalWrite (RELAY_DIRECTION2_PIN, LOW);
delay (RELAY_LAG);
digitalWrite (RELAY_RUNNING1_PIN, HIGH);
digitalWrite (RELAY_RUNNING2_PIN, HIGH);
} else { // is_closing
digitalWrite (RELAY_DIRECTION1_PIN, HIGH);
digitalWrite (RELAY_DIRECTION2_PIN, HIGH);
delay (RELAY_LAG);
digitalWrite (RELAY_RUNNING1_PIN, HIGH);
digitalWrite (RELAY_RUNNING2_PIN, HIGH);
}
int state = (((is_opening ? 1 : 0) << 1) | (is_running ? 1 : 0));
if (ostate != state) {
cmd_reply1 ("-Direction="); cmd_reply1 (state&2 ? "1" : "0");
cmd_reply1 (" Power="); cmd_reply1 (state&1 ? "1" : "0");
cmd_reply ("");
ostate = state;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment