diff options
author | Thomas Van Iseghem <[email protected]> | 2023-09-14 17:50:49 +0200 |
---|---|---|
committer | Thomas Van Iseghem <[email protected]> | 2023-09-14 17:50:49 +0200 |
commit | 3ba4308938bcab5a23ae96b0809895ef28d47720 (patch) | |
tree | 899b80068ee8ee67ce8a31f701c8c998fb8a6f36 | |
parent | 14fef5db770b3af8668d08524ae6fa19791b2cb3 (diff) | |
download | OpenCortex-3ba4308938bcab5a23ae96b0809895ef28d47720.tar.gz OpenCortex-3ba4308938bcab5a23ae96b0809895ef28d47720.zip |
Refactored stomp code + added websocket server for stomp control
-rw-r--r-- | Stomps/README.md | 14 | ||||
-rw-r--r-- | Stomps/SimpleWebSocketServer/SimpleWebSocketServer.py | 735 | ||||
-rw-r--r-- | Stomps/SimpleWebSocketServer/__init__.py | 3 | ||||
-rw-r--r-- | Stomps/read_stomp.py | 56 | ||||
-rw-r--r-- | Stomps/stomp.py (renamed from Stomps/write_stomp.py) | 64 | ||||
-rw-r--r-- | Stomps/stomp_control.py | 131 | ||||
-rw-r--r-- | Stomps/stomp_scroll.py | 23 | ||||
-rw-r--r-- | Stomps/stomp_server.py | 98 |
8 files changed, 931 insertions, 193 deletions
diff --git a/Stomps/README.md b/Stomps/README.md index da4fe92..9d995ec 100644 --- a/Stomps/README.md +++ b/Stomps/README.md @@ -1,3 +1,17 @@ +## Installation + +1) Take this folder `Stomps` and put it anywhere you like on the QC + +## Usage + +`cd` into the `Stomps` directory installed on the QC and run any of the Python scripts. + +There are a couple of fun things to play around with. +1) **read_stomp.py:** Allows you to decode and read stomp device traffic +2) **stomp_control.py:** CLI tool to activate stomps +3) **stomp_scroll.py:** Just a fun visual effect +4) **stomp_server.py:** Spawns a websocket server that allows you to control stomps over IP + ## Stomp message byte examples: **Action: button | Value: pressed** diff --git a/Stomps/SimpleWebSocketServer/SimpleWebSocketServer.py b/Stomps/SimpleWebSocketServer/SimpleWebSocketServer.py new file mode 100644 index 0000000..ea528ee --- /dev/null +++ b/Stomps/SimpleWebSocketServer/SimpleWebSocketServer.py @@ -0,0 +1,735 @@ +''' +The MIT License (MIT) +Copyright (c) 2013 Dave P. +''' +import sys +VER = sys.version_info[0] +if VER >= 3: + import socketserver + from http.server import BaseHTTPRequestHandler + from io import StringIO, BytesIO +else: + import SocketServer + from BaseHTTPServer import BaseHTTPRequestHandler + from StringIO import StringIO + +import hashlib +import base64 +import socket +import struct +import ssl +import errno +import codecs +from collections import deque +from select import select + +__all__ = ['WebSocket', + 'SimpleWebSocketServer', + 'SimpleSSLWebSocketServer'] + +def _check_unicode(val): + if VER >= 3: + return isinstance(val, str) + else: + return isinstance(val, basestring) + +class HTTPRequest(BaseHTTPRequestHandler): + def __init__(self, request_text): + if VER >= 3: + self.rfile = BytesIO(request_text) + else: + self.rfile = StringIO(request_text) + self.raw_requestline = self.rfile.readline() + self.error_code = self.error_message = None + self.parse_request() + +_VALID_STATUS_CODES = [1000, 1001, 1002, 1003, 1007, 1008, + 1009, 1010, 1011, 3000, 3999, 4000, 4999] + +HANDSHAKE_STR = ( + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: %(acceptstr)s\r\n\r\n" +) + +FAILED_HANDSHAKE_STR = ( + "HTTP/1.1 426 Upgrade Required\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Content-Type: text/plain\r\n\r\n" + "This service requires use of the WebSocket protocol\r\n" +) + +GUID_STR = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + +STREAM = 0x0 +TEXT = 0x1 +BINARY = 0x2 +CLOSE = 0x8 +PING = 0x9 +PONG = 0xA + +HEADERB1 = 1 +HEADERB2 = 3 +LENGTHSHORT = 4 +LENGTHLONG = 5 +MASK = 6 +PAYLOAD = 7 + +MAXHEADER = 65536 +MAXPAYLOAD = 33554432 + +class WebSocket(object): + + def __init__(self, server, sock, address): + self.server = server + self.client = sock + self.address = address + + self.handshaked = False + self.headerbuffer = bytearray() + self.headertoread = 2048 + + self.fin = 0 + self.data = bytearray() + self.opcode = 0 + self.hasmask = 0 + self.maskarray = None + self.length = 0 + self.lengtharray = None + self.index = 0 + self.request = None + self.usingssl = False + + self.frag_start = False + self.frag_type = BINARY + self.frag_buffer = None + self.frag_decoder = codecs.getincrementaldecoder('utf-8')(errors='strict') + self.closed = False + self.sendq = deque() + + self.state = HEADERB1 + + # restrict the size of header and payload for security reasons + self.maxheader = MAXHEADER + self.maxpayload = MAXPAYLOAD + + def handleMessage(self): + """ + Called when websocket frame is received. + To access the frame data call self.data. + + If the frame is Text then self.data is a unicode object. + If the frame is Binary then self.data is a bytearray object. + """ + pass + + def handleConnected(self): + """ + Called when a websocket client connects to the server. + """ + pass + + def handleClose(self): + """ + Called when a websocket server gets a Close frame from a client. + """ + pass + + def _handlePacket(self): + if self.opcode == CLOSE: + pass + elif self.opcode == STREAM: + pass + elif self.opcode == TEXT: + pass + elif self.opcode == BINARY: + pass + elif self.opcode == PONG or self.opcode == PING: + if len(self.data) > 125: + raise Exception('control frame length can not be > 125') + else: + # unknown or reserved opcode so just close + raise Exception('unknown opcode') + + if self.opcode == CLOSE: + status = 1000 + reason = u'' + length = len(self.data) + + if length == 0: + pass + elif length >= 2: + status = struct.unpack_from('!H', self.data[:2])[0] + reason = self.data[2:] + + if status not in _VALID_STATUS_CODES: + status = 1002 + + if len(reason) > 0: + try: + reason = reason.decode('utf8', errors='strict') + except: + status = 1002 + else: + status = 1002 + + self.close(status, reason) + return + + elif self.fin == 0: + if self.opcode != STREAM: + if self.opcode == PING or self.opcode == PONG: + raise Exception('control messages can not be fragmented') + + self.frag_type = self.opcode + self.frag_start = True + self.frag_decoder.reset() + + if self.frag_type == TEXT: + self.frag_buffer = [] + utf_str = self.frag_decoder.decode(self.data, final = False) + if utf_str: + self.frag_buffer.append(utf_str) + else: + self.frag_buffer = bytearray() + self.frag_buffer.extend(self.data) + + else: + if self.frag_start is False: + raise Exception('fragmentation protocol error') + + if self.frag_type == TEXT: + utf_str = self.frag_decoder.decode(self.data, final = False) + if utf_str: + self.frag_buffer.append(utf_str) + else: + self.frag_buffer.extend(self.data) + + else: + if self.opcode == STREAM: + if self.frag_start is False: + raise Exception('fragmentation protocol error') + + if self.frag_type == TEXT: + utf_str = self.frag_decoder.decode(self.data, final = True) + self.frag_buffer.append(utf_str) + self.data = u''.join(self.frag_buffer) + else: + self.frag_buffer.extend(self.data) + self.data = self.frag_buffer + + self.handleMessage() + + self.frag_decoder.reset() + self.frag_type = BINARY + self.frag_start = False + self.frag_buffer = None + + elif self.opcode == PING: + self._sendMessage(False, PONG, self.data) + + elif self.opcode == PONG: + pass + + else: + if self.frag_start is True: + raise Exception('fragmentation protocol error') + + if self.opcode == TEXT: + try: + self.data = self.data.decode('utf8', errors='strict') + except Exception as exp: + raise Exception('invalid utf-8 payload') + + self.handleMessage() + + + def _handleData(self): + # do the HTTP header and handshake + if self.handshaked is False: + + try: + data = self.client.recv(self.headertoread) + except (ssl.SSLWantReadError, ssl.SSLWantWriteError): + # SSL socket not ready to read yet, wait and try again + return + if not data: + raise Exception('remote socket closed') + + else: + # accumulate + self.headerbuffer.extend(data) + + if len(self.headerbuffer) >= self.maxheader: + raise Exception('header exceeded allowable size') + + # indicates end of HTTP header + if b'\r\n\r\n' in self.headerbuffer: + self.request = HTTPRequest(self.headerbuffer) + + # handshake rfc 6455 + try: + key = self.request.headers['Sec-WebSocket-Key'] + k = key.encode('ascii') + GUID_STR.encode('ascii') + k_s = base64.b64encode(hashlib.sha1(k).digest()).decode('ascii') + hStr = HANDSHAKE_STR % {'acceptstr': k_s} + self.sendq.append((BINARY, hStr.encode('ascii'))) + self.handshaked = True + self.handleConnected() + except Exception as e: + hStr = FAILED_HANDSHAKE_STR + self._sendBuffer(hStr.encode('ascii'), True) + self.client.close() + raise Exception('handshake failed: %s', str(e)) + + # else do normal data + else: + try: + data = self.client.recv(16384) + except (ssl.SSLWantReadError, ssl.SSLWantWriteError): + # SSL socket not ready to read yet, wait and try again + return + if not data: + raise Exception("remote socket closed") + + if VER >= 3: + for d in data: + self._parseMessage(d) + else: + for d in data: + self._parseMessage(ord(d)) + + def close(self, status = 1000, reason = u''): + """ + Send Close frame to the client. The underlying socket is only closed + when the client acknowledges the Close frame. + + status is the closing identifier. + reason is the reason for the close. + """ + try: + if self.closed is False: + close_msg = bytearray() + close_msg.extend(struct.pack("!H", status)) + if _check_unicode(reason): + close_msg.extend(reason.encode('utf-8')) + else: + close_msg.extend(reason) + + self._sendMessage(False, CLOSE, close_msg) + + finally: + self.closed = True + + + def _sendBuffer(self, buff, send_all = False): + size = len(buff) + tosend = size + already_sent = 0 + + while tosend > 0: + try: + # i should be able to send a bytearray + sent = self.client.send(buff[already_sent:]) + if sent == 0: + raise RuntimeError('socket connection broken') + + already_sent += sent + tosend -= sent + + except (ssl.SSLWantReadError, ssl.SSLWantWriteError): + # SSL socket not ready to send yet, wait and try again + if send_all: + continue + return buff[already_sent:] + + except socket.error as e: + # if we have full buffers then wait for them to drain and try again + if e.errno in [errno.EAGAIN, errno.EWOULDBLOCK]: + if send_all: + continue + return buff[already_sent:] + else: + raise e + + return None + + def sendFragmentStart(self, data): + """ + Send the start of a data fragment stream to a websocket client. + Subsequent data should be sent using sendFragment(). + A fragment stream is completed when sendFragmentEnd() is called. + + If data is a unicode object then the frame is sent as Text. + If the data is a bytearray object then the frame is sent as Binary. + """ + opcode = BINARY + if _check_unicode(data): + opcode = TEXT + self._sendMessage(True, opcode, data) + + def sendFragment(self, data): + """ + see sendFragmentStart() + + If data is a unicode object then the frame is sent as Text. + If the data is a bytearray object then the frame is sent as Binary. + """ + self._sendMessage(True, STREAM, data) + + def sendFragmentEnd(self, data): + """ + see sendFragmentEnd() + + If data is a unicode object then the frame is sent as Text. + If the data is a bytearray object then the frame is sent as Binary. + """ + self._sendMessage(False, STREAM, data) + + def sendMessage(self, data): + """ + Send websocket data frame to the client. + + If data is a unicode object then the frame is sent as Text. + If the data is a bytearray object then the frame is sent as Binary. + """ + opcode = BINARY + if _check_unicode(data): + opcode = TEXT + self._sendMessage(False, opcode, data) + + + def _sendMessage(self, fin, opcode, data): + + payload = bytearray() + + b1 = 0 + b2 = 0 + if fin is False: + b1 |= 0x80 + b1 |= opcode + + if _check_unicode(data): + data = data.encode('utf-8') + + length = len(data) + payload.append(b1) + + if length <= 125: + b2 |= length + payload.append(b2) + + elif length >= 126 and length <= 65535: + b2 |= 126 + payload.append(b2) + payload.extend(struct.pack("!H", length)) + + else: + b2 |= 127 + payload.append(b2) + payload.extend(struct.pack("!Q", length)) + + if length > 0: + payload.extend(data) + + self.sendq.append((opcode, payload)) + + + def _parseMessage(self, byte): + # read in the header + if self.state == HEADERB1: + + self.fin = byte & 0x80 + self.opcode = byte & 0x0F + self.state = HEADERB2 + + self.index = 0 + self.length = 0 + self.lengtharray = bytearray() + self.data = bytearray() + + rsv = byte & 0x70 + if rsv != 0: + raise Exception('RSV bit must be 0') + + elif self.state == HEADERB2: + mask = byte & 0x80 + length = byte & 0x7F + + if self.opcode == PING and length > 125: + raise Exception('ping packet is too large') + + if mask == 128: + self.hasmask = True + else: + self.hasmask = False + + if length <= 125: + self.length = length + + # if we have a mask we must read it + if self.hasmask is True: + self.maskarray = bytearray() + self.state = MASK + else: + # if there is no mask and no payload we are done + if self.length <= 0: + try: + self._handlePacket() + finally: + self.state = HEADERB1 + self.data = bytearray() + + # we have no mask and some payload + else: + #self.index = 0 + self.data = bytearray() + self.state = PAYLOAD + + elif length == 126: + self.lengtharray = bytearray() + self.state = LENGTHSHORT + + elif length == 127: + self.lengtharray = bytearray() + self.state = LENGTHLONG + + + elif self.state == LENGTHSHORT: + self.lengtharray.append(byte) + + if len(self.lengtharray) > 2: + raise Exception('short length exceeded allowable size') + + if len(self.lengtharray) == 2: + self.length = struct.unpack_from('!H', self.lengtharray)[0] + + if self.hasmask is True: + self.maskarray = bytearray() + self.state = MASK + else: + # if there is no mask and no payload we are done + if self.length <= 0: + try: + self._handlePacket() + finally: + self.state = HEADERB1 + self.data = bytearray() + + # we have no mask and some payload + else: + #self.index = 0 + self.data = bytearray() + self.state = PAYLOAD + + elif self.state == LENGTHLONG: + + self.lengtharray.append(byte) + + if len(self.lengtharray) > 8: + raise Exception('long length exceeded allowable size') + + if len(self.lengtharray) == 8: + self.length = struct.unpack_from('!Q', self.lengtharray)[0] + + if self.hasmask is True: + self.maskarray = bytearray() + self.state = MASK + else: + # if there is no mask and no payload we are done + if self.length <= 0: + try: + self._handlePacket() + finally: + self.state = HEADERB1 + self.data = bytearray() + + # we have no mask and some payload + else: + #self.index = 0 + self.data = bytearray() + self.state = PAYLOAD + + # MASK STATE + elif self.state == MASK: + self.maskarray.append(byte) + + if len(self.maskarray) > 4: + raise Exception('mask exceeded allowable size') + + if len(self.maskarray) == 4: + # if there is no mask and no payload we are done + if self.length <= 0: + try: + self._handlePacket() + finally: + self.state = HEADERB1 + self.data = bytearray() + + # we have no mask and some payload + else: + #self.index = 0 + self.data = bytearray() + self.state = PAYLOAD + + # PAYLOAD STATE + elif self.state == PAYLOAD: + if self.hasmask is True: + self.data.append( byte ^ self.maskarray[self.index % 4] ) + else: + self.data.append( byte ) + + # if length exceeds allowable size then we except and remove the connection + if len(self.data) >= self.maxpayload: + raise Exception('payload exceeded allowable size') + + # check if we have processed length bytes; if so we are done + if (self.index+1) == self.length: + try: + self._handlePacket() + finally: + #self.index = 0 + self.state = HEADERB1 + self.data = bytearray() + else: + self.index += 1 + + +class SimpleWebSocketServer(object): + def __init__(self, host, port, websocketclass, selectInterval = 0.1): + self.websocketclass = websocketclass + + if (host == ''): + host = None + + hostInfo = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_PASSIVE) + self.serversocket = socket.socket(socket.AF_INET, hostInfo[0][1], hostInfo[0][2]) + self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.serversocket.bind(hostInfo[0][4]) + self.serversocket.listen(5) + self.selectInterval = selectInterval + self.connections = {} + self.listeners = [self.serversocket] + + def _decorateSocket(self, sock): + return sock + + def _constructWebSocket(self, sock, address): + return self.websocketclass(self, sock, address) + + def close(self): + self.serversocket.close() + + for desc, conn in self.connections.items(): + conn.close() + self._handleClose(conn) + + def _handleClose(self, client): + client.client.close() + # only call handleClose when we have a successful websocket connection + if client.handshaked: + try: + client.handleClose() + except: + pass + + def serveonce(self): + writers = [] + for fileno in self.listeners: + if fileno == self.serversocket: + continue + client = self.connections[fileno] + if client.sendq: + writers.append(fileno) + + rList, wList, xList = select(self.listeners, writers, self.listeners, self.selectInterval) + + for ready in wList: + client = self.connections[ready] + try: + while client.sendq: + opcode, payload = client.sendq.popleft() + remaining = client._sendBuffer(payload) + if remaining is not None: + client.sendq.appendleft((opcode, remaining)) + break + else: + if opcode == CLOSE: + raise Exception('received client close') + + except Exception as n: + self._handleClose(client) + del self.connections[ready] + self.listeners.remove(ready) + + for ready in rList: + if ready == self.serversocket: + sock = None + try: + sock, address = self.serversocket.accept() + newsock = self._decorateSocket(sock) + newsock.setblocking(0) + fileno = newsock.fileno() + self.connections[fileno] = self._constructWebSocket(newsock, address) + self.listeners.append(fileno) + except Exception as n: + if sock is not None: + sock.close() + else: + if ready not in self.connections: + continue + client = self.connections[ready] + try: + client._handleData() + except Exception as n: + self._handleClose(client) + del self.connections[ready] + self.listeners.remove(ready) + + for failed in xList: + if failed == self.serversocket: + self.close() + raise Exception('server socket failed') + else: + if failed not in self.connections: + continue + client = self.connections[failed] + self._handleClose(client) + del self.connections[failed] + self.listeners.remove(failed) + + def serveforever(self): + while True: + self.serveonce() + +class SimpleSSLWebSocketServer(SimpleWebSocketServer): + + def __init__(self, host, port, websocketclass, certfile = None, + keyfile = None, version = ssl.PROTOCOL_TLSv1_2, selectInterval = 0.1, ssl_context = None): + + SimpleWebSocketServer.__init__(self, host, port, + websocketclass, selectInterval) + + if ssl_context is None: + self.context = ssl.SSLContext(version) + self.context.load_cert_chain(certfile, keyfile) + else: + self.context = ssl_context + + def close(self): + super(SimpleSSLWebSocketServer, self).close() + + def _decorateSocket(self, sock): + sslsock = self.context.wrap_socket(sock, server_side=True) + return sslsock + + def _constructWebSocket(self, sock, address): + ws = self.websocketclass(self, sock, address) + ws.usingssl = True + return ws + + def serveforever(self): + super(SimpleSSLWebSocketServer, self).serveforever() diff --git a/Stomps/SimpleWebSocketServer/__init__.py b/Stomps/SimpleWebSocketServer/__init__.py new file mode 100644 index 0000000..c7e52b8 --- /dev/null +++ b/Stomps/SimpleWebSocketServer/__init__.py @@ -0,0 +1,3 @@ +from .SimpleWebSocketServer import * + +name="SimpleWebSocketServer" diff --git a/Stomps/read_stomp.py b/Stomps/read_stomp.py index 65dcdae..d1ff7f0 100644 --- a/Stomps/read_stomp.py +++ b/Stomps/read_stomp.py @@ -1,61 +1,7 @@ # Quick and simple Python 2 script to read the raw bytes, # from a stomp device on the Quad Cortex. -from datetime import datetime - -class StompEvent: - def __init__(self, raw_bytes): - self.raw_bytes = raw_bytes - self.action = "unknown" - self.value = "unknown" - self.system_epoch = datetime.now().strftime("%s") - self.system_datetime = datetime.now() - self.decode_bytes() - - def print_bytes(self): - bytes_str = "" - for i in range(len(self.raw_bytes)): - bytes_str = bytes_str + self.raw_bytes[i].encode('hex') + ' ' - print bytes_str - print '---'*32 - - def decode_bytes(self): - self.decode_action() - self.decode_value() - self.decode_time() - - def decode_action(self): - action_byte_1 = self.raw_bytes[10].encode('hex') - action_byte_2 = self.raw_bytes[11].encode('hex') - if(action_byte_1 == "11" and action_byte_2 == "01"): - self.action = "button" - elif(action_byte_1 == "08" and action_byte_2 == "00"): - self.action = "rotary" - - def decode_value(self): - value_byte = self.raw_bytes[12].encode('hex') - if(value_byte == "00"): - self.value = "released" - elif(value_byte == "01"): - if(self.action == "button"): - self.value = "pressed" - elif(self.action == "rotary"): - self.value = "clock wise" - elif(value_byte == "ff"): - self.value = "counter clock" - - def decode_time(self): - # the first 4 bytes form the time - time_bytes = self.raw_bytes[0:4] - time_bytes.reverse() - time_hex = "" - for i in range(len(time_bytes)): - time_hex = time_hex + time_bytes[i].encode('hex') - self.epoch = int(time_hex, 16) - self.date_time = datetime.fromtimestamp(self.epoch) - - def __str__(self): - return "Action: " + self.action + "\nValue: " + self.value + "\nEpoch: " + str(self.epoch) + "\nDatetime: " + str(self.date_time) +from stomp import StompEvent with open('/dev/zencoder/knob_stomp2', 'rb') as file: byte_buffer = [] diff --git a/Stomps/write_stomp.py b/Stomps/stomp.py index 002acd4..e244ead 100644 --- a/Stomps/write_stomp.py +++ b/Stomps/stomp.py @@ -1,6 +1,3 @@ -# Quick and simple Python 2 script to write the raw bytes, -# to a stomp device on the Quad Cortex. - from datetime import datetime class StompCommand: @@ -58,19 +55,56 @@ class StompCommand: def __str__(self): return "Action: " + self.action + "\nValue: " + self.value + "\nBytes: " + self.bytes_to_hex() + + +class StompEvent: + def __init__(self, raw_bytes): + self.raw_bytes = raw_bytes + self.action = "unknown" + self.value = "unknown" + self.decode_bytes() + def print_bytes(self): + bytes_str = "" + for i in range(len(self.raw_bytes)): + bytes_str = bytes_str + self.raw_bytes[i].encode('hex') + ' ' + print bytes_str + print '---'*32 -with open('/dev/zencoder/knob_stomp2', 'wb') as file: - # write a button press - command = StompCommand("button", "pressed") - print command - file.write(command.raw_bytes) + def decode_bytes(self): + self.decode_action() + self.decode_value() + self.decode_time() - print '' + def decode_action(self): + action_byte_1 = self.raw_bytes[10].encode('hex') + action_byte_2 = self.raw_bytes[11].encode('hex') + if(action_byte_1 == "11" and action_byte_2 == "01"): + self.action = "button" + elif(action_byte_1 == "08" and action_byte_2 == "00"): + self.action = "rotary" - # write a button release - command = StompCommand("button", "released") - print command - file.write(command.raw_bytes) - # close the file - file.close()
\ No newline at end of file + def decode_value(self): + value_byte = self.raw_bytes[12].encode('hex') + if(value_byte == "00"): + self.value = "released" + elif(value_byte == "01"): + if(self.action == "button"): + self.value = "pressed" + elif(self.action == "rotary"): + self.value = "clock wise" + elif(value_byte == "ff"): + self.value = "counter clock" + + def decode_time(self): + # the first 4 bytes form the time + time_bytes = self.raw_bytes[0:4] + time_bytes.reverse() + time_hex = "" + for i in range(len(time_bytes)): + time_hex = time_hex + time_bytes[i].encode('hex') + self.epoch = int(time_hex, 16) + self.date_time = datetime.fromtimestamp(self.epoch) + + def __str__(self): + return "Action: " + self.action + "\nValue: " + self.value + "\nEpoch: " + str(self.epoch) + "\nDatetime: " + str(self.date_time) diff --git a/Stomps/stomp_control.py b/Stomps/stomp_control.py index add3dd8..86aae20 100644 --- a/Stomps/stomp_control.py +++ b/Stomps/stomp_control.py @@ -1,131 +1,16 @@ # Code copied to be easy to use as one script - -from datetime import datetime - -class StompCommand: - def __init__(self, action, value): - self.action = action - self.value = value - self.raw_bytes = bytearray(32) - self.encode_bytes() - - def encode_bytes(self): - self.encode_time() - self.encode_action() - self.encode_value() - self.build_last_16_bytes() - - def encode_time(self): - epoch = datetime.now().strftime("%s") - epoch_hex = hex(int(epoch))[2:] - epoch_bytes = bytearray.fromhex(epoch_hex) - epoch_bytes.reverse() - for i in range(4): - self.raw_bytes[i] = epoch_bytes[i] - - def encode_action(self): - self.raw_bytes[9] = 0x00 - if(self.action == "button"): - self.raw_bytes[8] = 0x01 - self.raw_bytes[10] = 0x11 - self.raw_bytes[11] = 0x01 - elif(self.action == "rotary"): - self.raw_bytes[8] = 0x01 - self.raw_bytes[10] = 0x08 - self.raw_bytes[11] = 0x00 - - def encode_value(self): - if(self.value == "pressed"): - self.raw_bytes[12] = 0x01 - elif(self.value == "released"): - self.raw_bytes[12] = 0x00 - elif(self.value == "clock wise"): - self.raw_bytes[12] = 0x01 - elif(self.value == "counter clock"): - self.raw_bytes[12] = 0xff - - def build_last_16_bytes(self): - # build the last 16 bytes - for i in range(6): - self.raw_bytes[16+i] = self.raw_bytes[i] - - def bytes_to_hex(self): - bytes_str = "" - for i in range(len(self.raw_bytes)): - bytes_str = bytes_str + str(self.raw_bytes[i]) + ' ' - return bytes_str - - def __str__(self): - return "Action: " + self.action + "\nValue: " + self.value + "\nBytes: " + self.bytes_to_hex() +from stomp import StompCommand -class StompEvent: - def __init__(self, raw_bytes): - self.raw_bytes = raw_bytes - self.action = "unknown" - self.value = "unknown" - self.system_epoch = datetime.now().strftime("%s") - self.system_datetime = datetime.now() - self.decode_bytes() - - def print_bytes(self): - bytes_str = "" - for i in range(len(self.raw_bytes)): - bytes_str = bytes_str + self.raw_bytes[i].encode('hex') + ' ' - print bytes_str - print '---'*32 - - def decode_bytes(self): - self.decode_action() - self.decode_value() - self.decode_time() - - def decode_action(self): - action_byte_1 = self.raw_bytes[10].encode('hex') - action_byte_2 = self.raw_bytes[11].encode('hex') - if(action_byte_1 == "11" and action_byte_2 == "01"): - self.action = "button" - elif(action_byte_1 == "08" and action_byte_2 == "00"): - self.action = "rotary" - - def decode_value(self): - value_byte = self.raw_bytes[12].encode('hex') - if(value_byte == "00"): - self.value = "released" - elif(value_byte == "01"): - if(self.action == "button"): - self.value = "pressed" - elif(self.action == "rotary"): - self.value = "clock wise" - elif(value_byte == "ff"): - self.value = "counter clock" - - def decode_time(self): - # the first 4 bytes form the time - time_bytes = self.raw_bytes[0:4] - time_bytes.reverse() - time_hex = "" - for i in range(len(time_bytes)): - time_hex = time_hex + time_bytes[i].encode('hex') - self.epoch = int(time_hex, 16) - self.date_time = datetime.fromtimestamp(self.epoch) - - def __str__(self): - return "Action: " + self.action + "\nValue: " + self.value + "\nEpoch: " + str(self.epoch) + "\nDatetime: " + str(self.date_time) - -# Open knob_stomp1 to 11, listen for numeric input and switch on input -dev_file_names = [] -for i in range(1,12): - dev_file_names.append('/dev/zencoder/knob_stomp' + str(i)) - def switch_stomp(selected_stomp): - with open(dev_file_names[selected_stomp-1], 'wb') as file: + with open('/dev/zencoder/knob_stomp' + str(selected_stomp), 'wb') as file: press_command = StompCommand("button", "pressed") file.write(press_command.raw_bytes) release_command = StompCommand("button", "released") file.write(release_command.raw_bytes) -while True: - selected_stomp = int(input("Enter a stomp between 1 and 11: ")) - if(selected_stomp > 0 and selected_stomp < 12): - print "Switching stomp " + str(selected_stomp) - switch_stomp(selected_stomp)
\ No newline at end of file +if __name__ == "__main__": + while True: + selected_stomp = int(input("Enter a stomp between 1 and 11: ")) + if(selected_stomp > 0 and selected_stomp < 12): + print "Switching stomp " + str(selected_stomp) + switch_stomp(selected_stomp)
\ No newline at end of file diff --git a/Stomps/stomp_scroll.py b/Stomps/stomp_scroll.py new file mode 100644 index 0000000..c10ddd2 --- /dev/null +++ b/Stomps/stomp_scroll.py @@ -0,0 +1,23 @@ +from stomp import StompCommand + +# Open knob_stomp1 to 11, listen for numeric input and switch on input +dev_file_names = [] +for i in range(1,12): + dev_file_names.append('/dev/zencoder/knob_stomp' + str(i)) + +def switch_stomp(selected_stomp): + with open(dev_file_names[selected_stomp-1], 'wb') as file: + press_command = StompCommand("button", "pressed") + file.write(press_command.raw_bytes) + release_command = StompCommand("button", "released") + file.write(release_command.raw_bytes) + +selected_stomp = 1 +while True: + if(selected_stomp > 0 and selected_stomp < 10): + if selected_stomp != 5: + print "Switching stomp " + str(selected_stomp) + switch_stomp(selected_stomp) + selected_stomp = selected_stomp + 1 + else: + selected_stomp = 1
\ No newline at end of file diff --git a/Stomps/stomp_server.py b/Stomps/stomp_server.py new file mode 100644 index 0000000..9ef213b --- /dev/null +++ b/Stomps/stomp_server.py @@ -0,0 +1,98 @@ +''' +The MIT License (MIT) +Copyright (c) 2013 Dave P. +''' + +import signal +import sys +import ssl +from SimpleWebSocketServer import WebSocket, SimpleWebSocketServer, SimpleSSLWebSocketServer +from optparse import OptionParser +import json +from stomp_control import switch_stomp + +class StompServer(WebSocket): + + # Request is a JSON object with the following fields: + # { + # "type": "button", + # "action": "activate", + # "index": 1 + # } + + def handleMessage(self): + print "Received" + self.data + + try: + json_request = json.loads(self.data) + except ValueError: + self.sendMessage("Invalid request") + return + print "JSON request: " + str(json_request) + "\n Validating..." + + try: + type = json_request['type'] + action = json_request['action'] + index = json_request['index'] + except: + self.sendMessage("Invalid request") + return + + print "Type: " + type + ", action: " + action + ", index: " + str(index) + + if type == 'button': + if action == 'activate': + switch_stomp(int(index)) + + self.sendMessage("Success") + + def handleConnected(self): + print (self.address, 'connected') + + def handleClose(self): + print (self.address, 'closed') + + def validate_request(req): + if not req: + return False + if not req['type']: + return False + if not req['action']: + return False + if not req['index']: + return False + + if req['type'] != 'button' and req['type'] != 'rotary': + return False + if req['action'] != 'activate': + return False + if req['index'] < 1 or req['index'] > 11: + return False + return True + + +if __name__ == "__main__": + parser = OptionParser(usage="usage: %prog [options]", version="%prog 1.0") + parser.add_option("--host", default='', type='string', action="store", dest="host", help="hostname (localhost)") + parser.add_option("--port", default=8000, type='int', action="store", dest="port", help="port (8000)") + parser.add_option("--ssl", default=0, type='int', action="store", dest="ssl", help="ssl (1: on, 0: off (default))") + parser.add_option("--cert", default='./cert.pem', type='string', action="store", dest="cert", help="cert (./cert.pem)") + parser.add_option("--key", default='./key.pem', type='string', action="store", dest="key", help="key (./key.pem)") + parser.add_option("--ver", default=ssl.PROTOCOL_TLSv1, type=int, action="store", dest="ver", help="ssl version") + + (options, args) = parser.parse_args() + print "Starting server on port " + str(options.port) + cls = StompServer + + if options.ssl == 1: + server = SimpleSSLWebSocketServer(options.host, options.port, cls, options.cert, options.key, version=options.ver) + else: + server = SimpleWebSocketServer(options.host, options.port, cls) + + def close_sig_handler(signal, frame): + server.close() + sys.exit() + + signal.signal(signal.SIGINT, close_sig_handler) + + server.serveforever() |