Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Replace old doorbot code with new code from @Nutter

  • Loading branch information...
commit f36099b5860890b100f7704ad0dfbf01ceba5d51 1 parent cc252a3
Luke Closs authored
View
18 README.md
@@ -0,0 +1,18 @@
+VHS Door V2
+===========
+
+Sends GET requests to a web server whenever the door opens or closes, and parses the JSON response.
+
+Developed with Arduino 1.0.2, and definitely won't work with anything older than 1.0.1, due to use of the Ethernet.maintain() function (there are other applicable bug fixes in 1.0.1 also).
+
+## Circuit ##
+
+- Ethernet shield attached to pins 10, 11, 12, 13
+- Door connected to pin 7 and ground (open=floating, closed=held low. Internal pullup is used)
+
+The code lives in doorbot/
+
+## Required Libraries ##
+Requires the aJSON library for Arduino: [http://github.com/interactive-matter/aJson](http://github.com/interactive-matter/aJson)
+
+See the Arduino Libraries Guide here for how to install a library: [http://arduino.cc/en/Guide/Libraries](http://arduino.cc/en/Guide/Libraries)
View
249 arduino_door_tweet/Makefile
@@ -1,249 +0,0 @@
-# Arduino 0011 Makefile
-# Arduino adaptation by mellis, eighthave, oli.keller
-#
-# This makefile allows you to build sketches from the command line
-# without the Arduino environment (or Java).
-#
-# Detailed instructions for using the makefile:
-#
-# 1. Copy this file into the folder with your sketch. There should be a
-# file with the extension .cpp (e.g. foo.cpp)
-#
-# 2. Below, modify the line containing "TARGET" to refer to the name of
-# of your program's file without an extension (e.g. TARGET = foo).
-#
-# 3. Modify the line containg "INSTALL_DIR" to point to the directory that
-# contains the Arduino installation (for example, under Mac OS X, this
-# might be /Applications/arduino-0011).
-#
-# 4. Modify the line containing "PORT" to refer to the filename
-# representing the USB or serial connection to your Arduino board
-# (e.g. PORT = /dev/tty.USB0). If the exact name of this file
-# changes, you can use * as a wildcard (e.g. PORT = /dev/tty.USB*).
-#
-# 5. Set the line containing "MCU" to match your board's processor.
-# Older one's are atmega8 based, newer ones like Arduino Mini, Bluetooth
-# or Diecimila have the atmega168. If you're using a LilyPad Arduino,
-# change F_CPU to 8000000.
-#
-# 6. At the command line, change to the directory containing your
-# program's file and the makefile.
-#
-# 7. Type "make" and press enter to compile/verify your program.
-#
-# 8. Type "make upload", reset your Arduino board, and press enter to
-# upload your program to the Arduino board.
-#
-# $Id$
-
-TARGET = arduino_door_tweet
-INSTALL_DIR = /usr/local/share/arduino-0015/
-PORT = /dev/ttyUSB0
-UPLOAD_RATE = 19200
-AVRDUDE_PROGRAMMER = stk500v1
-MCU = atmega168
-F_CPU = 16000000
-
-############################################################################
-# Below here nothing should be changed...
-
-ARDUINO = $(INSTALL_DIR)/hardware/cores/arduino
-AVR_TOOLS_PATH = $(INSTALL_DIR)/hardware/tools/avr/bin
-SRC = $(ARDUINO)/pins_arduino.c $(ARDUINO)/wiring.c \
-$(ARDUINO)/wiring_analog.c $(ARDUINO)/wiring_digital.c \
-$(ARDUINO)/wiring_pulse.c $(ARDUINO)/wiring_serial.c \
-$(ARDUINO)/wiring_shift.c $(ARDUINO)/WInterrupts.c
-CXXSRC = $(ARDUINO)/HardwareSerial.cpp $(ARDUINO)/WMath.cpp $(ARDUINO)/Print.cpp
-FORMAT = ihex
-
-
-# Name of this Makefile (used for "make depend").
-MAKEFILE = Makefile
-
-# Debugging format.
-# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2.
-# AVR (extended) COFF requires stabs, plus an avr-objcopy run.
-DEBUG = stabs
-
-OPT = s
-
-# Place -D or -U options here
-CDEFS = -DF_CPU=$(F_CPU)
-CXXDEFS = -DF_CPU=$(F_CPU)
-
-# Place -I options here
-CINCS = -I$(ARDUINO)
-CXXINCS = -I$(ARDUINO)
-
-# Compiler flag to set the C Standard level.
-# c89 - "ANSI" C
-# gnu89 - c89 plus GCC extensions
-# c99 - ISO C99 standard (not yet fully implemented)
-# gnu99 - c99 plus GCC extensions
-CSTANDARD = -std=gnu99
-CDEBUG = -g$(DEBUG)
-CWARN = -Wall -Wstrict-prototypes
-CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
-#CEXTRA = -Wa,-adhlns=$(<:.c=.lst)
-
-CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA)
-CXXFLAGS = $(CDEFS) $(CINCS) -O$(OPT)
-#ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs
-LDFLAGS = -lm
-
-
-# Programming support using avrdude. Settings and variables.
-AVRDUDE_PORT = $(PORT)
-AVRDUDE_WRITE_FLASH = -U flash:w:applet/$(TARGET).hex
-AVRDUDE_FLAGS = -V -F -C $(INSTALL_DIR)/hardware/tools/avrdude.conf \
--p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) \
--b $(UPLOAD_RATE)
-
-# Program settings
-CC = avr-gcc
-CXX = avr-g++
-OBJCOPY = avr-objcopy
-OBJDUMP = avr-objdump
-AR = avr-ar
-SIZE = avr-size
-NM = avr-nm
-AVRDUDE = avrdude
-REMOVE = rm -f
-MV = mv -f
-
-# Define all object files.
-OBJ = $(SRC:.c=.o) $(CXXSRC:.cpp=.o) $(ASRC:.S=.o)
-
-# Define all listing files.
-LST = $(ASRC:.S=.lst) $(CXXSRC:.cpp=.lst) $(SRC:.c=.lst)
-
-# Combine all necessary flags and optional flags.
-# Add target processor to flags.
-ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS)
-ALL_CXXFLAGS = -mmcu=$(MCU) -I. $(CXXFLAGS)
-ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)
-
-
-# Default target.
-all: applet_files build sizeafter
-
-build: elf hex
-
-$(TARGET).cpp: $(TARGET).pde
- cp $(TARGET).pde $(TARGET).cpp
-
-applet_files: $(TARGET).cpp
- # Here is the "preprocessing".
- # It creates a .cpp file based with the same name as the .cpp file.
- # On top of the new .cpp file comes the WProgram.h header.
- # At the end there is a generic main() function attached.
- # Then the .cpp file will be compiled. Errors during compile will
- # refer to this new, automatically generated, file.
- # Not the original .cpp file you actually edit...
- test -d applet || mkdir applet
- echo '#include "WProgram.h"' > applet/$(TARGET).cpp
- cat $(TARGET).cpp >> applet/$(TARGET).cpp
- cat $(ARDUINO)/main.cxx >> applet/$(TARGET).cpp
-
-elf: applet/$(TARGET).elf
-hex: applet/$(TARGET).hex
-eep: applet/$(TARGET).eep
-lss: applet/$(TARGET).lss
-sym: applet/$(TARGET).sym
-
-# Program the device.
-upload: applet/$(TARGET).hex
- $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH)
-
-
- # Display size of file.
-HEXSIZE = $(SIZE) --target=$(FORMAT) applet/$(TARGET).hex
-ELFSIZE = $(SIZE) applet/$(TARGET).elf
-sizebefore:
- @if [ -f applet/$(TARGET).elf ]; then echo; echo $(MSG_SIZE_BEFORE); $(HEXSIZE); echo; fi
-
-sizeafter:
- @if [ -f applet/$(TARGET).elf ]; then echo; echo $(MSG_SIZE_AFTER); $(HEXSIZE); echo; fi
-
-
-# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
-COFFCONVERT=$(OBJCOPY) --debugging \
---change-section-address .data-0x800000 \
---change-section-address .bss-0x800000 \
---change-section-address .noinit-0x800000 \
---change-section-address .eeprom-0x810000
-
-
-coff: applet/$(TARGET).elf
- $(COFFCONVERT) -O coff-avr applet/$(TARGET).elf $(TARGET).cof
-
-
-extcoff: $(TARGET).elf
- $(COFFCONVERT) -O coff-ext-avr applet/$(TARGET).elf $(TARGET).cof
-
-
-.SUFFIXES: .elf .hex .eep .lss .sym
-
-.elf.hex:
- $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
-
-.elf.eep:
- -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
- --change-section-lma .eeprom=0 -O $(FORMAT) $< $@
-
-# Create extended listing file from ELF output file.
-.elf.lss:
- $(OBJDUMP) -h -S $< > $@
-
-# Create a symbol table from ELF output file.
-.elf.sym:
- $(NM) -n $< > $@
-
- # Link: create ELF output file from library.
-applet/$(TARGET).elf: $(TARGET).cpp applet/core.a
- $(CC) $(ALL_CFLAGS) -o $@ applet/$(TARGET).cpp -L. applet/core.a $(LDFLAGS)
-
-applet/core.a: $(OBJ)
- test -d applet || mkdir applet
- @for i in $(OBJ); do echo $(AR) rcs applet/core.a $$i; $(AR) rcs applet/core.a $$i; done
-
-
-
-# Compile: create object files from C++ source files.
-.cpp.o:
- $(CXX) -c $(ALL_CXXFLAGS) $< -o $@
-
-# Compile: create object files from C source files.
-.c.o:
- $(CC) -c $(ALL_CFLAGS) $< -o $@
-
-
-# Compile: create assembler files from C source files.
-.c.s:
- $(CC) -S $(ALL_CFLAGS) $< -o $@
-
-
-# Assemble: create object files from assembler source files.
-.S.o:
- $(CC) -c $(ALL_ASFLAGS) $< -o $@
-
-
-
-# Target: clean project.
-clean:
- $(REMOVE) applet/$(TARGET).hex applet/$(TARGET).cpp applet/$(TARGET).eep applet/$(TARGET).cof applet/$(TARGET).elf \
- applet/$(TARGET).map applet/$(TARGET).sym applet/$(TARGET).lss applet/core.a \
- $(OBJ) $(LST) $(SRC:.c=.s) $(SRC:.c=.d) $(CXXSRC:.cpp=.s) $(CXXSRC:.cpp=.d)
-
-depend:
- if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \
- then \
- sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \
- $(MAKEFILE).$$$$ && \
- $(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \
- fi
- echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \
- >> $(MAKEFILE); \
- $(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE)
-
-.PHONY: all build elf hex eep lss sym program coff extcoff clean depend applet_files sizebefore sizeafter
View
209 arduino_door_tweet/arduino_door_tweet.pde
@@ -1,209 +0,0 @@
-/* Door Tweet
- gang programmed by VHS (c) copyright 2009.
- goldfish vanjuggler tristan danv
- see LICENSE file for full details.
-*/
-
-#define doorPin (4)
-#define bathroomDoorPin (7)
-#define tempPin (0)
-#define buzzerHighPin (11)
-#define INPUT_BUFFER(S) (strcmp((S), buffer) == 0)
-
-// Incoming serial msg buffer
-#define BUFSIZE 256
-char buffer[BUFSIZE];
-int buffer_idx = 0;
-
-// Musical note table, frequencies in Hz
-int NOTES[] = {
- 2093, // C7
- 2349, // D7
- 2637, // E7
- 2794, // F7
- 3136, // G7
- 3520, // A7
- 3951, // B7
- 4186, // C8
-};
-
-int doorPinState;
-int bathroomDoorPinState;
-
-void setup() {
- Serial.begin( 9600 );
-
- // set input pins & pull-ups
- pinMode( doorPin, INPUT );
- pinMode( tempPin, INPUT );
- pinMode( bathroomDoorPin, INPUT );
- digitalWrite( doorPin, HIGH );
- digitalWrite( tempPin, HIGH );
- digitalWrite( bathroomDoorPin, HIGH );
-
- // reset piezo
- digitalWrite( buzzerHighPin, LOW);
-
- doorPinState = digitalRead( doorPin );
- bathroomDoorPinState = digitalRead( bathroomDoorPin );
-
- // transform note frequencies to periods
- for (int i=0; i < 8; ++i) {
- NOTES[i] = 1e6 / NOTES[i];
- }
-}
-
-/// Read temperature from supplied pin
-float readTemperature(int pin)
-{
- int temp = analogRead(tempPin);
- float voltage = temp * 5.0f / 1024;
- float celsius = (voltage - 0.5f) * 100;
- return celsius;
-}
-
-/// Check door status and update global doorPinState
-int checkDoor() {
- int newDoorPinState;
-
- newDoorPinState = digitalRead( doorPin );
- if( doorPinState != newDoorPinState ){
- doorPinState = newDoorPinState;
- return 1;
- }
- return 0;
-}
-
-/// Check door status and update global doorPinState
-int checkBathroomDoor() {
- int newBathroomDoorPinState;
-
- newBathroomDoorPinState = !digitalRead( bathroomDoorPin );
- if( bathroomDoorPinState != newBathroomDoorPinState ){
- bathroomDoorPinState = newBathroomDoorPinState;
- return 1;
- }
- return 0;
-}
-
-/// Check serial port for incoming messages and update global buffer
-int checkSerialCommand() {
- int pending = 0;
-
- if (buffer_idx == 0) {
- memset(buffer, 0, BUFSIZE);
- }
-
- // Read an incoming command
- // if bytes are available, addthemto the buffer
- int incoming = Serial.available();
- while (incoming-- > 0) {
- char foo = Serial.read();
-
- if (foo == '\n') {
- pending = 1;
- buffer[buffer_idx++] = 0;
- buffer_idx = 0;
- Serial.print("#RECV: ");
- Serial.println( buffer );
- }
- else {
- buffer[buffer_idx++] = foo;
- buffer[buffer_idx] = 0;
- }
- }
-
- return pending;
-}
-
-/**
- * Print a door's state to serial line
- *
- * @param doorState pointer to door state to check
- * @param doorName name of door to use in serial output
- */
-void print_door_state(int* doorState, const char* doorName)
-{
- if( *doorState == 0 ) {
- // door open
- Serial.print(doorName);
- Serial.println( " open" );
- // digitalWrite( 13, HIGH ); ???
- }
- else {
- // door closed
- Serial.print(doorName);
- Serial.println( " closed" );
- // digitalWrite( 13, LOW ); ???
- }
-}
-
-/**
- * Sound a given note on the buzzer for a specified duration
- *
- * @param note note table index (0-7)
- * @param duration duration of buzz in microseconds
- */
-int buzz(int note, long duration)
-{
- if (note >= 0 && note < 8) {
- int period = NOTES[note] / 2;
- long elapsed_time = 0;
-
- // while (elapsed_time < duration) {
- // square wave
- // digitalWrite(buzzerHighPin, HIGH);
- // delayMicroseconds(period);
- // digitalWrite(buzzerHighPin, LOW);
- // delayMicroseconds(period);
- // elapsed_time += period;
-
- // XXX the above code doesn't work yet, so here's a bodgy fallback
- analogWrite(11, 128);
- delay(1000);
- analogWrite(11, 0);
-
- // }
- return 1;
- }
- else {
- Serial.println("buzz note out of range");
- }
- // invalid note, return fail
- return 0;
-}
-
-void loop() {
- const char* door_name = "door";
- const char* bathroom_door_name = "bathroom";
-
- // Poll & push events to serial
- if (checkDoor()) {
- print_door_state(&doorPinState, door_name);
- }
-
- if (checkBathroomDoor()) {
- print_door_state(&bathroomDoorPinState, bathroom_door_name);
- }
-
- // Respond to incoming serial commands
- if (checkSerialCommand()) {
- if (INPUT_BUFFER("temperature")) {
- float temp = readTemperature(tempPin);
- Serial.print("temperature ");
- Serial.print(temp);
- Serial.println('C');
- }
- else if (INPUT_BUFFER("door state")) {
- print_door_state(&doorPinState, door_name);
- }
- else if (INPUT_BUFFER("bathroom door state")) {
- print_door_state(&bathroomDoorPinState, bathroom_door_name);
- }
- else if (INPUT_BUFFER("buzz")) {
- buzz(0, 2e6);
- Serial.println("buzz played");
- }
- }
-}
-
View
329 doorbot/doorbot.ino
@@ -0,0 +1,329 @@
+/*
+ VHS Doorbot V2
+ Modified from Arduino WebClient example
+
+ Circuit:
+ * Ethernet shield attached to pins 10, 11, 12, 13
+ * Door connected to pin 7 and ground (open=floating, closed=held low. Internal pullup is used)
+
+ */
+
+#include <SPI.h>
+#include <Ethernet.h>
+#include <aJSON.h>
+
+
+const int kDoorPin = 7;
+// const int kFooPin = 2; // int0 = pin2, int1 = pin3 (on most Arduinos...)
+const char* kHostName = "api.hackspace.ca";
+IPAddress server(108,171,189,39);
+
+// MAC address to use for the Ethernet shield.
+// Newer Ethernet shields have a MAC address printed on a sticker on the shield, otherwise this is arbitrary (should be unique though)
+byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEB, 0xDA, 0xED };
+
+
+EthernetClient client;
+int DoorState = -1;
+// int FooVal = -2;
+// int FooState = -1;
+// ...
+
+void setup()
+{
+ // Open serial communications and wait for port to open:
+ Serial.begin(9600);
+ while (!Serial)
+ ; // wait for serial port to connect. Needed for Leonardo only
+
+ // start the Ethernet connection:
+ Serial.println("Initializing Ethernet/DHCP...");
+ if (Ethernet.begin(mac) == 0)
+ {
+ Serial.println("Failed to configure Ethernet using DHCP");
+ // no point in carrying on, so do nothing forevermore:
+ for(;;)
+ ;
+ }
+
+ // give the Ethernet shield a second to initialize:
+ Serial.println("Waiting for ethernet/DHCP to init...");
+ delay(1000);
+
+ Serial.print("IP address is: ");
+ Serial.println(Ethernet.localIP());
+ Serial.println();
+
+ pinMode(kDoorPin, INPUT_PULLUP);
+ // attachInterrupt(0, OnFooChange, CHANGE);
+
+ Serial.println("Running...");
+ Serial.println();
+}
+
+void SendAPIGETRequest(const char* key, const char* value)
+{
+ char getCommand[128];
+ sprintf(getCommand, "GET /s/vhs/data/%s/update?value=%s&apikey=monkey HTTP/1.1", key, value);
+ char hostCommand[128];
+ sprintf(hostCommand, "Host: %s", kHostName);
+
+ client.println(getCommand);
+ client.println(hostCommand);
+ client.println();
+}
+
+char* ReadBytes(char* buffer, int numBytes)
+{
+ for (int i=0; i<numBytes; i++)
+ *buffer++ = client.read();
+ return buffer;
+}
+
+bool IsMalformed(char* data, char** pHeader, char** pBody)
+{
+ // Check if there was no end of the header
+ char* header = data;
+ char* body = strstr(header, "\r\n\r\n");
+ *pHeader = header;
+ *pBody = body;
+ if (body == NULL)
+ return true;
+
+ // NULL terminate the header and advance the body to where it actually begins
+ *body++ = '\0';
+ *body++ = '\0';
+ *body++ = '\0';
+ *body++ = '\0';
+
+ *pBody = body;
+
+ if (strstr(header, "HTTP/1.1 200 OK") == NULL)
+ return true;
+
+ return false;
+}
+
+int ReadServerResponse(char* pBuffer, int bufferSize, bool* pMalformed)
+{
+ bool malformed = *pMalformed;
+
+ int bytesRead = 0;
+ while (client.connected())
+ {
+ // if there are incoming bytes available from the server, read them
+ int bytesWaiting = client.available();
+ if ((bytesRead + bytesWaiting) > bufferSize)
+ {
+ Serial.println("Too much data was sent by the server; assuming an error occurred.");
+ malformed = true;
+ break;
+ }
+ else if (bytesWaiting)
+ {
+ pBuffer = ReadBytes(pBuffer, bytesWaiting);
+ bytesRead += bytesWaiting;
+ }
+ }
+ *pBuffer = '\0';
+
+ *pMalformed = malformed;
+ return bytesRead;
+}
+
+bool ParseServerResponse(char* buffer, bool* pMalformed)
+{
+ bool result = false;
+ bool malformed = *pMalformed;
+
+ char* header = NULL;
+ char* body = NULL;
+ if (!malformed)
+ malformed = IsMalformed(buffer, &header, &body);
+
+ if (!malformed)
+ {
+ int headerSize = strlen(header);
+ int bodySize = strlen(body);
+
+ aJsonObject* root = aJson.parse(body);
+
+ if (root != NULL)
+ {
+ aJsonObject* status = aJson.getObjectItem(root, "status");
+ if ((status == NULL) || (status->type != aJson_String) || (strcmp(status->valuestring, "OK") != 0))
+ malformed = true;
+ else
+ result = true;
+ }
+ else
+ {
+ malformed = true;
+ }
+
+ aJson.deleteItem(root);
+ }
+
+ *pMalformed = malformed;
+ return result;
+}
+
+bool UpdateAPIServer(const char* apiKey, const char* apiValue)
+{
+ bool result = false;
+
+ Serial.println("Waiting for ethernet client to be ready...");
+ while (!client)
+ ;
+
+ Serial.print("Connecting to server... ");
+
+ if (client.connect(server, 80))
+ {
+ Serial.println("connected.");
+
+ // Make a HTTP GET request with the status of the API
+ SendAPIGETRequest(apiKey, apiValue);
+
+ // Read the servers response
+ bool malformed = false;
+ char buffer[512];
+ int bytesRead = ReadServerResponse(buffer, sizeof(buffer), &malformed);
+
+ if (bytesRead > 0)
+ {
+ Serial.print(buffer);
+
+ if (ParseServerResponse(buffer, &malformed))
+ {
+ // API server update was successful!
+ Serial.println("Server successully updated.");
+
+ result = true;
+ }
+ else
+ {
+ if (malformed)
+ Serial.println("Server update failed: server error or malformed data was read. Trying again...");
+ else
+ Serial.println("Server update failed: server response status was invalid or indicated failure. Trying again...");
+ }
+ }
+ else
+ {
+ Serial.println("Server update failed: no data was read. Trying again...");
+ }
+ }
+ else
+ {
+ Serial.println("connection failed.");
+ }
+
+ Serial.println("Disconnecting.");
+ client.stop();
+
+ return result;
+}
+
+bool MaintainDHCPLease()
+{
+ Serial.print("Maintaining DHCP lease... ");
+ int maintainResult = Ethernet.maintain();
+ switch (maintainResult)
+ {
+ case DHCP_CHECK_NONE:
+ Serial.println("nothing to do.");
+ break;
+
+ case DHCP_CHECK_RENEW_OK:
+ Serial.println("successfully renewed.");
+ break;
+ case DHCP_CHECK_REBIND_OK:
+ Serial.println("successfully rebound.");
+ break;
+
+ case DHCP_CHECK_RENEW_FAIL:
+ Serial.println("renew failed. Trying again...");
+ break;
+ case DHCP_CHECK_REBIND_FAIL:
+ Serial.println("rebind failed. Trying again...");
+ break;
+ };
+
+ if ((maintainResult == DHCP_CHECK_RENEW_OK) || (maintainResult == DHCP_CHECK_REBIND_OK))
+ {
+ Serial.print("IP address is: ");
+ Serial.println(Ethernet.localIP());
+ Serial.println();
+ }
+
+ bool leaseValid = (maintainResult != DHCP_CHECK_RENEW_FAIL) && (maintainResult != DHCP_CHECK_REBIND_FAIL);
+ return leaseValid;
+}
+
+/*
+void OnFooChange()
+{
+ FooVal = digitalRead(kFooPin);
+}
+*/
+
+void loop()
+{
+ int doorVal;
+ do
+ {
+ // Note: Any changes with a short period (button presses, etc) should use interrupts.
+ // There's only 2 hw interrupt pins though, so avoid them if possible so they can be
+ // used for other purposes in the future.
+
+ doorVal = digitalRead(kDoorPin);
+ // ...
+ } while ((doorVal == DoorState) /* && (FooVal == FooState) ... */);
+
+ // Refresh the Ethernet Shield with the DNS server (DHCP)
+ bool leaseValid = MaintainDHCPLease();
+
+ // Don't attempt to update the server is the LAN connection is down
+ if (leaseValid)
+ {
+ if (doorVal != DoorState)
+ {
+ char* apiValue;
+ if (doorVal != 0)
+ {
+ Serial.println("Space is OPEN!");
+ apiValue = "open";
+ }
+ else
+ {
+ Serial.println("Space is CLOSED!");
+ apiValue = "closed";
+ }
+
+ if (UpdateAPIServer("door", apiValue))
+ DoorState = doorVal;
+ }
+
+/*
+ //
+ if (FooVal != FooState)
+ {
+ ...
+ FooState = FooVal;
+ }
+
+ ...
+*/
+ }
+
+ //
+ // Wait a bit before checking the status again
+ delay(1000);
+ Serial.println();
+ Serial.println();
+ Serial.println();
+ Serial.println();
+ Serial.println();
+}
+
View
79 sensor/mockdoor.pde
@@ -1,79 +0,0 @@
-/* Mock Door Tweet
- gang programmed by VHS (c) copyright 2009.
- goldfish vanjuggler tristan danv
- see LICENSE file for full details.
-*/
-
-#define doorPin (4)
-#define bathroomDoorPin (7)
-#define tempPin (0)
-#define INPUT_BUFFER(S) (strcmp((S), buffer) == 0)
-
-// Incoming serial msg buffer
-#define BUFSIZE 256
-char buffer[BUFSIZE];
-int buffer_idx = 0;
-
-void setup() {
- Serial.begin( 9600 );
-}
-
-/// Check serial port for incoming messages and update global buffer
-int checkSerialCommand() {
- int pending = 0;
-
- if (buffer_idx == 0) {
- memset(buffer, 0, BUFSIZE);
- }
-
- // Read an incoming command
- // if bytes are available, addthemto the buffer
- int incoming = Serial.available();
- while (incoming-- > 0) {
- char foo = Serial.read();
-
- if (foo == '\n') {
- pending = 1;
- buffer[buffer_idx++] = 0;
- buffer_idx = 0;
- Serial.print("#RECV: ");
- Serial.println(buffer);
- }
- else {
- buffer[buffer_idx++] = foo;
- buffer[buffer_idx] = 0;
- }
- }
-
- return pending;
-}
-
-int cycles = 0;
-bool doorOpen = false;
-
-void loop() {
- if (checkSerialCommand()) {
- if (INPUT_BUFFER("temperature")) {
- Serial.println("temperature 24.44C");
- }
- else if (INPUT_BUFFER("buzz")) {
- Serial.println("buzz played");
- }
- else {
- Serial.print(buffer);
- Serial.println(" !unknown command");
- }
- }
-
- if (++cycles % 100 == 0) {
- if (doorOpen) {
- Serial.println("door open");
- }
- else {
- Serial.println("door closed");
- }
- doorOpen = !doorOpen;
- }
-
- delay(100);
-}
View
278 sensor/serialserver.py
@@ -1,278 +0,0 @@
-#!/usr/bin/env python
-
-"""
-serialserver multiplexes the Arduino serial port to concurrent incoming TCP
-socket connections. this allows multiple clients to use the connected arduino
-at once.
-
-socket server listens on port 9994 and keeps open all incoming TCP connections
-
-messages sent to the open socket are passed to the serial port and responses
- are written to the client socket
-
-the serial port is also polled periodically and any other messages are broadcast
- to all open connections
-
-future improvements:
- - subclass SocketServer.TCPServer constructor to eliminate global SERIAL_DAEMON
- - switch from Lock protected lists to python's synchronized Queue class
- - better granularity of existing locks
-"""
-
-from __future__ import with_statement
-
-import serial, yaml
-import SocketServer, threading, socket, re, os, logging, time
-import traceback
-
-SERVER_HOST_PORT = 'localhost', 9994
-
-SERIAL_PORT, LOG_FILENAME, YAML_CONFIG = {
- 'nt': ('COM12', 'serialserver.log', 'vhs.yaml'),
-}.get(os.name, ('/dev/ttyUSB0', '/var/log/vhs-serialserver.log', '/etc/vhs.yaml'))
-
-config = yaml.load(file(YAML_CONFIG))
-SENSOR_HOOKS_DIR = config.get('sensor_hooks_dir')
-
-# socket read timeout in seconds
-TIMEOUT = 0.01
-# serial port timeout in seconds
-SERIAL_TIMEOUT = 0.05
-
-global SERIAL_DAEMON
-
-logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
-
-class TCPHandler(SocketServer.BaseRequestHandler):
- def setup(self):
- global SERIAL_DAEMON
- assert(SERIAL_DAEMON)
-
- self.seriald = SERIAL_DAEMON
-
- (host, port) = self.client_address
- self.client_id = port
- self.seriald.register_client(self.client_id)
-
- self.request.settimeout(TIMEOUT)
-
- def handle(self):
- logging.info('Opened TCP client connection %s' % str(self.client_address))
-
- while True:
- try:
- self.data = self.request.recv(1024)
-
- if self.data:
- message = self.data.strip()
- if not message: continue
-
- logging.debug('received message "%s" length %d' % (message, len(message)))
- # send to serial port
- self.seriald.outgoing((self.client_id, message))
- else:
- # stream closed
- self.request.close()
- logging.info('Closed TCP client connection #%d' % self.client_id)
- break
- except socket.timeout:
- # check if there are any incoming messages for this client
- messages = self.seriald.incoming(self.client_id, pop=True)
- for (id, msg) in messages:
- logging.debug('sending message %s to %d' % (msg.strip(), self.client_id))
- self.request.send(msg)
-
- def finish(self):
- self.seriald.unregister_client(self.client_id)
-
-class TCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
- pass
-
-class SerialDaemon(object):
- def __init__(self, ser):
- assert(ser)
-
- self.ser = ser # serial port
- self.incoming_serial = [] # message queue from the serial port
- self.outgoing_serial = [] # message queue writing to serial port
- self.clients = set() # track a global list of clients for broadcast messages
-
- self.client_lock = threading.Lock() # clients mutex
- self.message_lock = threading.Lock() # message queues mutex
-
- def register_client(self, client_id):
- with self.client_lock:
- self.clients.add(client_id)
-
- def unregister_client(self, client_id):
- logging.debug('unregistering client %s' % client_id)
- with self.client_lock:
- self.clients.remove(client_id)
-
- # remove any messages related to this client
- self.incoming(client_id, pop=True)
- with self.message_lock:
- self.outgoing_serial = filter(lambda (id, msg): id != client_id,
- self.outgoing_serial)
-
- def update(self):
- """Called from main loop"""
-
- valid_message = lambda msg: msg and not msg.startswith('#')
-
- def broadcast(message):
- for client in self.clients:
- self.incoming_serial.append((client, msg))
-
- # read and broadcast any new global messages
- msg = self.ser.readline()
- if valid_message(msg):
- with self.message_lock: broadcast(msg)
-
- # send client requests and accumulate responses
- with self.message_lock:
- while len(self.outgoing_serial):
- (id, msg) = self.outgoing_serial.pop(0)
- logging.debug('writing "%s" to serial port' % msg)
- self.ser.write(msg + '\n')
-
- # wait till we get a response to our query
- if valid_message(msg):
- attempts = 100
- command = msg.split()[0]
-
- while attempts:
- response = self.ser.readline()
- if valid_message(response):
- logging.debug('response "%s"' % response.strip())
- logging.debug(' command "%s"' % command)
- if response and command == response.split()[0]:
- logging.debug('matched response %s' % response.strip())
- self.incoming_serial.append((id, response))
- break
- else:
- broadcast(msg)
-
- attempts -= 1
- else:
- # send out a timeout message
- logging.warning('timeout on message "%s"' % msg)
- self.incoming_serial.append((id, '!timeout\r\n'))
-
- def outgoing(self, (client_id, message)):
- """Send a message to the serial port"""
-
- with self.message_lock:
- self.outgoing_serial.append((client_id, message))
-
- def incoming(self, client_id, pop=True):
- """
- Get incoming messages from the serial port
-
- client_id -- matches only incoming messages associated with the given
- client id
- pop -- remove matching messages from the incoming queue
- """
-
- messages = list()
- popped_queue = list()
-
- with self.message_lock:
- for msg in self.incoming_serial:
- if msg[0] == client_id:
- messages.append(msg)
- else:
- popped_queue.append(msg)
- if pop:
- self.incoming_serial = popped_queue
-
- return messages
-
-class RelayScript(object):
- """
- RelayScript opens a server port and listens for commands from the arduino,
- then triggers matching scripts in SENSOR_HOOKS_DIR
- """
-
- def __init__(self):
- logging.debug('Relay will trigger scripts from %s' % SENSOR_HOOKS_DIR)
-
- def loop(self):
- retry = False
-
- def _loop():
- while True:
- try:
- data = self.socket.recv(1024)
- if data:
- self.run_command_from_arduino(data)
- logging.debug('Received data "%s"' % data.strip())
- else:
- logging.info('Client connection terminated by server')
- retry = True
- break
- except socket.timeout:
- pass
-
- while True:
- self.connect()
- _loop()
- if retry:
- logging.info('Relay client waiting 10s before trying to reconnect')
- time.sleep(10)
- else:
- break
-
- def connect(self):
- # connect to socket
- self.socket = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.settimeout(TIMEOUT)
- s.connect(SERVER_HOST_PORT)
-
- def run_command_from_arduino(self, data):
- def trigger(arg, dirname, names):
- for filename in names:
- script_path = os.path.join(dirname, filename)
- if not os.path.islink(script_path) and os.path.isfile(script_path):
- full_path = '%s %s' % (script_path, arg)
- logging.debug('launching "%s"' % full_path)
- os.system(full_path)
-
- command, sep, arg = data.strip().partition(' ')
- hookpath = SENSOR_HOOKS_DIR
- script_dir = os.path.join(hookpath, '%s.d' % command)
- if os.path.exists(script_dir):
- os.path.walk(script_dir, trigger, arg)
-
-if __name__ == '__main__':
- ser = serial.Serial(SERIAL_PORT, 9600, timeout=SERIAL_TIMEOUT)
-
- try:
- global SERIAL_DAEMON
- SERIAL_DAEMON = SerialDaemon(ser)
-
- # start up server thread
- server = TCPServer(SERVER_HOST_PORT, TCPHandler)
- server_thread = threading.Thread(target=server.serve_forever)
- server_thread.setDaemon(True)
- server_thread.start()
-
- # start up relay client thread
- relay = RelayScript()
- relay_thread = threading.Thread(target=relay.loop)
- relay_thread.setDaemon(True)
- relay_thread.start()
-
- logging.info('Serial server initialized')
- logging.info(' -- listening on serial port %s and %s\n' % (
- SERIAL_PORT, '%s:%d' % (SERVER_HOST_PORT)))
- while True:
- # poll serial port
- SERIAL_DAEMON.update()
-
- except Exception, e:
- logging.critical('serialserver exception:', e)
- logging.critical(traceback.format_exc())
- finally:
- ser.close()
-
View
53 sensor/serialserverd
@@ -1,53 +0,0 @@
-#!/bin/sh
-
-# init.d script for serial server on democracy
-# XXX hacked to start the server as user 'danv' but any member of dialout will do
-# relevant start-stop-daemon args are:
-# -m : make a pidfile
-# --pidfile : name of pidfile
-# -b : run in background (detach)
-# -c : set uid of process
-# when stopping match only on pid (for now)
-
-PATH=/usr/local/vhs/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/local/vhs/bin/serialserver.py
-NAME=serialserverd
-DESC=serialserverd
-
-test -x $DAEMON || exit 0
-
-set -e
-
-. /lib/lsb/init-functions
-
-case "$1" in
- start)
- echo -n "Starting $DESC: "
- start-stop-daemon --start --quiet -m --pidfile /var/run/$NAME.pid -b -c danv \
- --exec $DAEMON -- $DAEMON_OPTS || true
- echo "$NAME."
- ;;
- stop)
- echo -n "Stopping $DESC: "
- start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid || true
- echo "$NAME."
- ;;
- restart)
- echo -n "Restarting $DESC: "
- start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid || true
- sleep 1
- start-stop-daemon --start --quiet -m --pidfile /var/run/$NAME.pid -b -c danv \
- --exec $DAEMON -- $DAEMON_OPTS || true
- echo "$NAME."
- ;;
- status)
- status_of_proc -p /var/run/$NAME.pid "$DAEMON" vhs-door && exit 0 || exit $?
- ;;
- *)
- N=/etc/init.d/$NAME
- echo "Usage: $N {start|stop|restart|status}" >&2
- exit 1
- ;;
-esac
-
-exit 0
View
54 sensor/www/decorators.py
@@ -1,54 +0,0 @@
-# danv - some decorators for restricting access to URLs
-
-import web, shelve, datetime
-
-# restricted URL's require this key to be passed in as a GET or
-# POST param.
-SECRET_KEY='7787855982'
-
-# use the @restricted decorator to protect sensitive URLs
-def restricted(view):
- def _wrapper(*args, **kw):
- params = web.input(key=None)
- if params.key != SECRET_KEY:
- return '!restricted URL - for VHS members only'
- return view(*args, **kw)
- return _wrapper
-
-SHELF_PATH = '/tmp/sensor.shelf'
-def throttled(timeout=10, everyone=True):
- """
- Throttle access to a view
-
- timeout -- minimum wait time between responses
- everyone -- if True, apply throttling to authorized clients as well
- """
-
- def _decorator(view):
- def _wrapper(*args, **kw):
- params = web.input(key=None)
-
- if params.key != SECRET_KEY or everyone:
- # check to see if a recent response is available
- shelf = shelve.open(SHELF_PATH)
-
- now = datetime.datetime.now()
- if shelf.has_key(view.__name__):
- (timestamp, previous_response) = shelf[view.__name__]
- if (now - timestamp).seconds <= timeout:
- return previous_response
-
- response = view(*args, **kw)
- shelf[view.__name__] = (now, response)
- shelf.sync()
- return response
-
- else:
- # authorized clients - no worries!
- return view(*args, **kw)
-
- return _wrapper
-
- return _decorator
-
-
View
252 sensor/www/sensor.py
@@ -1,252 +0,0 @@
-#!/usr/bin/env python
-
-import web, serial
-import os, re, socket, datetime, hashlib, httplib
-
-import yaml
-import xml.dom.minidom
-
-import decree # http://bitbucket.org/dantakk/decree
-
-from decorators import restricted, throttled
-
-TIMEOUT = 30
-
-DOC = """\
-sensor.hackspace.ca - a <a href="http://vancouver.hackspace.ca">VHS</a> project
-
- <a href="/door/state">/door/state</a>
- text/plain response: open|closed (entrance door)
-
- <a href="/door/photo">/door/photo</a>
- image/jpeg response: a photo of the entrance taken in the last %ds
-
- <a href="/bathroom/door/state">/bathroom/door/state</a>
- text/plain response: open|closed (bathroom door)
-
- <a href="/temperature/celsius">/temperature/celsius</a>
- text/plain response: (\d+(\.\d*)?) (space temperature in celsius)
-
- <a href="/temperature/fahrenheit">/temperature/fahrenheit</a>
- text/plain response: (\d+\.(\d*)?) (space temperature in fahrenheit)
-
- <a href="/feed/eeml">/feed/eeml</a>
- text/xml response: an <a href="http://www.eeml.org">EEML</a> XML feed for use with <a href="http://www.pachube.com">pachube</a>
-
-Restricted urls require VHS membership
-
- <a href="/buzz">/buzz</a>
- text/plain response: (raw buzzer response)
-
- <a href="/door/photo/url">/door/photo/url</a>
- image/jpeg response: redirects to URL of a new photo of the entrance
-""" % TIMEOUT
-
-SERIAL_HOST_PORT = ('localhost', 9994)
-
-# route urls to handler classes
-urls = (
- r'/door/(state|photo(?:\/url)?)/?', 'Door',
- r'/bathroom/door/(state)/?', 'BathroomDoor',
- r'/temperature/(celsius|fahrenheit)/?', 'Temperature',
- r'/buzz/?', 'Buzz',
- r'/feed/(eeml)/?', 'Feed',
- r'/feed/(pusheeml)/?', 'Feed',
- r'.*', 'Static',
-)
-
-app = web.application(urls, globals())
-
-def serial_query(query):
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect(SERIAL_HOST_PORT)
- s.send(query + '\n')
- response = s.recv(1024)
- s.close()
- return response
-
-def get_celsius():
- response = serial_query('temperature')
- match = re.search(r'((\d+)(\.\d+)?)C?', response)
- if match:
- return match.groups()[0]
- else:
- return None
-
-def take_door_photo():
- """Take a photo of the front door and return a URL and file path"""
-
- # based on lukec's code in VHS.pm
- config = yaml.load(file('/etc/vhs.yaml'))
- short_hash = hashlib.sha256(str(datetime.datetime.now())).hexdigest()[0:6]
- pic_base = config.get('picture_base')
- if pic_base:
- filename = os.path.join(pic_base, '%s.jpeg' % short_hash)
- os.system('streamer -c /dev/video0 -b 16 -o %s >/dev/null 2>&1' % filename)
- short_file = os.path.splitext(filename)[0] + '.jpg'
- os.rename(filename, short_file)
- pic_uri_base = config.get('picture_uri_base')
- if pic_uri_base and os.path.exists(short_file):
- pic_uri = '%s/%s' % (pic_uri_base, os.path.basename(short_file))
- return (pic_uri, short_file)
-
- return None
-
-class Door(object):
- def __init__(self, *args, **kw):
- super(Door, self).__init__(*args, **kw)
- self.doorname = self.response_cmd = 'door'
-
- def door_state(self):
- response = serial_query('%s state' % self.doorname).strip()
- if response == '%s closed' % self.response_cmd:
- return 'closed'
- elif response == '%s open' % self.response_cmd:
- return 'opened'
- else:
- return '!unknown response <%s>' % response
-
- @throttled(timeout=TIMEOUT)
- def door_photo(self):
- door_photo_uri, door_photo_path = take_door_photo()
- if door_photo_path:
- web.header('Content-Type', 'image/jpeg')
- web.header('X-Vhs-URL', str(door_photo_uri))
- return(file(door_photo_path).read())
-
- return '!door photo not taken - check config'
-
- @restricted
- def door_photo_url(self):
- door_photo_uri, door_photo_path = take_door_photo()
- if door_photo_uri:
- raise web.seeother(door_photo_uri)
-
- return '!door photo not taken - check config'
-
- def GET(self, query):
- if query == 'state':
- return self.door_state()
- elif query == 'photo':
- return self.door_photo()
- elif query == 'photo/url':
- return self.door_photo_url()
-
-class BathroomDoor(Door):
- def __init__(self, *args, **kw):
- super(BathroomDoor, self).__init__(*args, **kw)
- self.doorname = 'bathroom door'
- self.response_cmd = 'bathroom'
-
-class Temperature:
- @property
- def celsius(self):
- return get_celsius() or '#no match in response "%s"' % response
-
- @property
- def fahrenheit(self):
- celsius = self.celsius
-
- if celsius.startswith('#'):
- return celsius
-
- fahr = 1.8 * float(celsius) + 32
- return '%.2f' % fahr
-
- def GET(self, scale):
- if scale == 'celsius':
- return self.celsius
- elif scale == 'fahrenheit':
- return self.fahrenheit
-
-class Static:
- def GET(self):
- web.header('Content-Type', 'text/html')
- return """\
-<html>
- <head>
- <title>sensor.hackspace.ca</title>
- </head>
- <body>
- <pre>%s</pre>
- </body>
-</html>
-""" % DOC
-
-class Buzz:
- @restricted
- def GET(self):
- return serial_query('buzz');
-
-class Feed:
- @property
- def eeml(self):
- tag = decree.DecreeTagger()
- xmlns = { 'xmlns': 'http://www.eeml.org/xsd/005',
- 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
- 'xsi:schemaLocation': 'http://www.eeml.org/xad/005 http://www.eeml.org/xsd/005/005.xsd',
- }
-
- try:
- celsius = float(get_celsius())
- except ValueError:
- celsius = None
-
- # TODO include properly formatted date
- feed = tag.eeml[xmlns] (
- tag.environment (
- tag.title('Vancouver Hackerspace (VHS)'),
- # tag.feed('http://www..com/feeds/1.xml'),
- tag.description('VHS @ 45 West Hastings St'),
- tag.website('http://vancouver.hackspace.ca'),
- tag.email('info@hackspace.ca'),
- tag.location[{ 'exposure': 'indoor',
- 'domain': 'physical',
- 'disposition': 'fixed'}] (
- tag.name('VHS first floor'),
- tag.lat('49.282319'),
- tag.lon('-123.106152'),
- ),
- tag.data[{'id':'0'}] (
- tag.tag('temperature'),
- tag.value(celsius),
- tag.unit[{'symbol': 'C', 'type': 'derivedSI'}]('Celsius')
- ) if celsius else None,
- )
- )
- builder = decree.XmlDomBuilder(xml.dom.minidom.getDOMImplementation())
- doc = builder.create_xml_dom(feed)
- # for debugging return doc.toprettyxml()
- return doc.toxml()
-
- def pachube_update(self):
- config = yaml.load(file('/etc/vhs.yaml'))
- pachube_apikey = config.get('pachube_apikey')
- if pachube_apikey:
- conn = httplib.HTTPConnection('www.pachube.com:80')
- conn.request('PUT', 'http://www.pachube.com/api/2417.xml', self.eeml, {'X-PachubeApiKey': pachube_apikey})
- rsp = conn.getresponse()
- if rsp.status != 200:
- raise Exception(rsp.reason)
- response = rsp.read()
- conn.close()
-
- return response
-
- return 'pachube_apikey not defined in /etc/vhs.yaml'
-
- def GET(self, kind):
- if kind == 'eeml':
- web.header('Content-Type', 'text/xml')
- return self.eeml
- elif kind == 'pusheeml':
- web.header('Content-Type', 'text/plain')
- try:
- return self.pachube_update()
- except Exception, e:
- return 'Error: %s' % e
-
-
-if __name__ == '__main__':
- app.run()
-
Please sign in to comment.
Something went wrong with that request. Please try again.