Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Luke Closs <gravatar@5thplane.com>
- Loading branch information
1 parent
471caaf
commit 3ffa07d
Showing
3 changed files
with
511 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
#!/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: | ||
- convert print's to logging.* | ||
- 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 | ||
import SocketServer, threading, socket, re, os, logging | ||
import traceback | ||
|
||
SERVER_HOST_PORT = 'localhost', 9994 | ||
|
||
SERIAL_PORT = 'COM12' if os.name is 'nt' else '/dev/ttyUSB0' | ||
|
||
# socket read timeout in seconds | ||
TIMEOUT = 0.01 | ||
# serial port timeout in seconds | ||
SERIAL_TIMEOUT = 0.05 | ||
|
||
global SERIAL_DAEMON | ||
|
||
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): | ||
print '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 | ||
|
||
print 'received message', message, 'length %d' % len(message) | ||
# send to serial port | ||
self.seriald.outgoing((self.client_id, message)) | ||
else: | ||
# stream closed | ||
self.request.close() | ||
print '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: | ||
print '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): | ||
print 'unregistering client ', 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) | ||
print '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): | ||
print 'response "%s"\ncommand "%s"' % (response.strip(), command) | ||
if response and command == response.split()[0]: | ||
print 'matched response %s' % response.strip() | ||
self.incoming_serial.append((id, response)) | ||
break | ||
else: | ||
broadcast(msg) | ||
|
||
attempts -= 1 | ||
else: | ||
# send out a timeout message | ||
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 | ||
|
||
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() | ||
|
||
print 'Serial server initialized' | ||
print ' -- 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: | ||
print 'serialserver exception:', e | ||
traceback.print_exc() | ||
finally: | ||
ser.close() | ||
|
Oops, something went wrong.