initial commit, just echoing
This commit is contained in:
commit
30c9169a0c
6 changed files with 459 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
wifi.py
|
23
boot.py
Normal file
23
boot.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# This file is executed on every boot (including wake-boot from deepsleep)
|
||||
#import esp
|
||||
#esp.osdebug(None)
|
||||
import uos, machine
|
||||
#uos.dupterm(None, 1) # disable REPL on UART(0)
|
||||
import gc
|
||||
#import webrepl
|
||||
#webrepl.start()
|
||||
gc.collect()
|
||||
|
||||
# SSID=xxx, PASSPHRASE=yyy
|
||||
from wifi import *
|
||||
import network
|
||||
import utime as time
|
||||
|
||||
sta_if = network.WLAN(network.STA_IF)
|
||||
sta_if.active(True)
|
||||
sta_if.connect(SSID, PASSPHRASE)
|
||||
print('\nconnecting...')
|
||||
while not sta_if.isconnected():
|
||||
print('.')
|
||||
time.sleep(1)
|
||||
print("ifconfig: {}".format(sta_if.ifconfig()))
|
94
lib/logging.py
Normal file
94
lib/logging.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
import sys
|
||||
|
||||
CRITICAL = 50
|
||||
ERROR = 40
|
||||
WARNING = 30
|
||||
INFO = 20
|
||||
DEBUG = 10
|
||||
NOTSET = 0
|
||||
|
||||
_level_dict = {
|
||||
CRITICAL: "CRIT",
|
||||
ERROR: "ERROR",
|
||||
WARNING: "WARN",
|
||||
INFO: "INFO",
|
||||
DEBUG: "DEBUG",
|
||||
}
|
||||
|
||||
_stream = sys.stderr
|
||||
|
||||
class Logger:
|
||||
|
||||
level = NOTSET
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def _level_str(self, level):
|
||||
l = _level_dict.get(level)
|
||||
if l is not None:
|
||||
return l
|
||||
return "LVL%s" % level
|
||||
|
||||
def setLevel(self, level):
|
||||
self.level = level
|
||||
|
||||
def isEnabledFor(self, level):
|
||||
return level >= (self.level or _level)
|
||||
|
||||
def log(self, level, msg, *args):
|
||||
if level >= (self.level or _level):
|
||||
_stream.write("%s:%s:" % (self._level_str(level), self.name))
|
||||
if not args:
|
||||
print(msg, file=_stream)
|
||||
else:
|
||||
print(msg % args, file=_stream)
|
||||
|
||||
def debug(self, msg, *args):
|
||||
self.log(DEBUG, msg, *args)
|
||||
|
||||
def info(self, msg, *args):
|
||||
self.log(INFO, msg, *args)
|
||||
|
||||
def warning(self, msg, *args):
|
||||
self.log(WARNING, msg, *args)
|
||||
|
||||
def error(self, msg, *args):
|
||||
self.log(ERROR, msg, *args)
|
||||
|
||||
def critical(self, msg, *args):
|
||||
self.log(CRITICAL, msg, *args)
|
||||
|
||||
def exc(self, e, msg, *args):
|
||||
self.log(ERROR, msg, *args)
|
||||
sys.print_exception(e, _stream)
|
||||
|
||||
def exception(self, msg, *args):
|
||||
self.exc(sys.exc_info()[1], msg, *args)
|
||||
|
||||
|
||||
_level = INFO
|
||||
_loggers = {}
|
||||
|
||||
def getLogger(name):
|
||||
if name in _loggers:
|
||||
return _loggers[name]
|
||||
l = Logger(name)
|
||||
_loggers[name] = l
|
||||
return l
|
||||
|
||||
def info(msg, *args):
|
||||
getLogger(None).info(msg, *args)
|
||||
|
||||
def debug(msg, *args):
|
||||
getLogger(None).debug(msg, *args)
|
||||
|
||||
def basicConfig(level=INFO, filename=None, stream=None, format=None):
|
||||
global _level, _stream
|
||||
_level = level
|
||||
if stream:
|
||||
_stream = stream
|
||||
if filename is not None:
|
||||
print("logging.basicConfig: filename arg is not supported")
|
||||
if format is not None:
|
||||
print("logging.basicConfig: format arg is not supported")
|
68
lib/uwebsockets/client.py
Normal file
68
lib/uwebsockets/client.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Websockets client for micropython
|
||||
|
||||
Based very heavily off
|
||||
https://github.com/aaugustin/websockets/blob/master/websockets/client.py
|
||||
"""
|
||||
|
||||
import logging
|
||||
import usocket as socket
|
||||
import ubinascii as binascii
|
||||
import urandom as random
|
||||
import ussl
|
||||
|
||||
from .protocol import Websocket, urlparse
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebsocketClient(Websocket):
|
||||
is_client = True
|
||||
|
||||
def connect(uri):
|
||||
"""
|
||||
Connect a websocket.
|
||||
"""
|
||||
|
||||
uri = urlparse(uri)
|
||||
assert uri
|
||||
|
||||
if __debug__: LOGGER.debug("open connection %s:%s",
|
||||
uri.hostname, uri.port)
|
||||
|
||||
sock = socket.socket()
|
||||
addr = socket.getaddrinfo(uri.hostname, uri.port)
|
||||
sock.connect(addr[0][4])
|
||||
if uri.protocol == 'wss':
|
||||
sock = ussl.wrap_socket(sock)
|
||||
|
||||
def send_header(header, *args):
|
||||
if __debug__: LOGGER.debug(str(header), *args)
|
||||
sock.write(header % args + '\r\n')
|
||||
|
||||
# Sec-WebSocket-Key is 16 bytes of random base64 encoded
|
||||
key = binascii.b2a_base64(bytes(random.getrandbits(8)
|
||||
for _ in range(16)))[:-1]
|
||||
|
||||
send_header(b'GET %s HTTP/1.1', uri.path or '/')
|
||||
send_header(b'Host: %s:%s', uri.hostname, uri.port)
|
||||
send_header(b'Connection: Upgrade')
|
||||
send_header(b'Upgrade: websocket')
|
||||
send_header(b'Sec-WebSocket-Key: %s', key)
|
||||
send_header(b'Sec-WebSocket-Version: 13')
|
||||
send_header(b'Origin: http://{hostname}:{port}'.format(
|
||||
hostname=uri.hostname,
|
||||
port=uri.port)
|
||||
)
|
||||
send_header(b'')
|
||||
|
||||
header = sock.readline()[:-2]
|
||||
assert header.startswith(b'HTTP/1.1 101 '), header
|
||||
|
||||
# We don't (currently) need these headers
|
||||
# FIXME: should we check the return key?
|
||||
while header:
|
||||
if __debug__: LOGGER.debug(str(header))
|
||||
header = sock.readline()[:-2]
|
||||
|
||||
return WebsocketClient(sock)
|
244
lib/uwebsockets/protocol.py
Normal file
244
lib/uwebsockets/protocol.py
Normal file
|
@ -0,0 +1,244 @@
|
|||
"""
|
||||
Websockets protocol
|
||||
"""
|
||||
|
||||
import logging
|
||||
import ure as re
|
||||
import ustruct as struct
|
||||
import urandom as random
|
||||
import usocket as socket
|
||||
from ucollections import namedtuple
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Opcodes
|
||||
OP_CONT = const(0x0)
|
||||
OP_TEXT = const(0x1)
|
||||
OP_BYTES = const(0x2)
|
||||
OP_CLOSE = const(0x8)
|
||||
OP_PING = const(0x9)
|
||||
OP_PONG = const(0xa)
|
||||
|
||||
# Close codes
|
||||
CLOSE_OK = const(1000)
|
||||
CLOSE_GOING_AWAY = const(1001)
|
||||
CLOSE_PROTOCOL_ERROR = const(1002)
|
||||
CLOSE_DATA_NOT_SUPPORTED = const(1003)
|
||||
CLOSE_BAD_DATA = const(1007)
|
||||
CLOSE_POLICY_VIOLATION = const(1008)
|
||||
CLOSE_TOO_BIG = const(1009)
|
||||
CLOSE_MISSING_EXTN = const(1010)
|
||||
CLOSE_BAD_CONDITION = const(1011)
|
||||
|
||||
URL_RE = re.compile(r'(wss|ws)://([A-Za-z0-9-\.]+)(?:\:([0-9]+))?(/.+)?')
|
||||
URI = namedtuple('URI', ('protocol', 'hostname', 'port', 'path'))
|
||||
|
||||
class NoDataException(Exception):
|
||||
pass
|
||||
|
||||
class ConnectionClosed(Exception):
|
||||
pass
|
||||
|
||||
def urlparse(uri):
|
||||
"""Parse ws:// URLs"""
|
||||
match = URL_RE.match(uri)
|
||||
if match:
|
||||
protocol = match.group(1)
|
||||
host = match.group(2)
|
||||
port = match.group(3)
|
||||
path = match.group(4)
|
||||
|
||||
if protocol == 'wss':
|
||||
if port is None:
|
||||
port = 443
|
||||
elif protocol == 'ws':
|
||||
if port is None:
|
||||
port = 80
|
||||
else:
|
||||
raise ValueError('Scheme {} is invalid'.format(protocol))
|
||||
|
||||
return URI(protocol, host, int(port), path)
|
||||
|
||||
|
||||
class Websocket:
|
||||
"""
|
||||
Basis of the Websocket protocol.
|
||||
This can probably be replaced with the C-based websocket module, but
|
||||
this one currently supports more options.
|
||||
"""
|
||||
is_client = False
|
||||
|
||||
def __init__(self, sock):
|
||||
self.sock = sock
|
||||
self.open = True
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
self.close()
|
||||
|
||||
def settimeout(self, timeout):
|
||||
self.sock.settimeout(timeout)
|
||||
|
||||
def read_frame(self, max_size=None):
|
||||
"""
|
||||
Read a frame from the socket.
|
||||
See https://tools.ietf.org/html/rfc6455#section-5.2 for the details.
|
||||
"""
|
||||
|
||||
# Frame header
|
||||
two_bytes = self.sock.read(2)
|
||||
|
||||
if not two_bytes:
|
||||
raise NoDataException
|
||||
|
||||
byte1, byte2 = struct.unpack('!BB', two_bytes)
|
||||
|
||||
# Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4)
|
||||
fin = bool(byte1 & 0x80)
|
||||
opcode = byte1 & 0x0f
|
||||
|
||||
# Byte 2: MASK(1) LENGTH(7)
|
||||
mask = bool(byte2 & (1 << 7))
|
||||
length = byte2 & 0x7f
|
||||
|
||||
if length == 126: # Magic number, length header is 2 bytes
|
||||
length, = struct.unpack('!H', self.sock.read(2))
|
||||
elif length == 127: # Magic number, length header is 8 bytes
|
||||
length, = struct.unpack('!Q', self.sock.read(8))
|
||||
|
||||
if mask: # Mask is 4 bytes
|
||||
mask_bits = self.sock.read(4)
|
||||
|
||||
try:
|
||||
data = self.sock.read(length)
|
||||
except MemoryError:
|
||||
# We can't receive this many bytes, close the socket
|
||||
if __debug__: LOGGER.debug("Frame of length %s too big. Closing",
|
||||
length)
|
||||
self.close(code=CLOSE_TOO_BIG)
|
||||
return True, OP_CLOSE, None
|
||||
|
||||
if mask:
|
||||
data = bytes(b ^ mask_bits[i % 4]
|
||||
for i, b in enumerate(data))
|
||||
|
||||
return fin, opcode, data
|
||||
|
||||
def write_frame(self, opcode, data=b''):
|
||||
"""
|
||||
Write a frame to the socket.
|
||||
See https://tools.ietf.org/html/rfc6455#section-5.2 for the details.
|
||||
"""
|
||||
fin = True
|
||||
mask = self.is_client # messages sent by client are masked
|
||||
|
||||
length = len(data)
|
||||
|
||||
# Frame header
|
||||
# Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4)
|
||||
byte1 = 0x80 if fin else 0
|
||||
byte1 |= opcode
|
||||
|
||||
# Byte 2: MASK(1) LENGTH(7)
|
||||
byte2 = 0x80 if mask else 0
|
||||
|
||||
if length < 126: # 126 is magic value to use 2-byte length header
|
||||
byte2 |= length
|
||||
self.sock.write(struct.pack('!BB', byte1, byte2))
|
||||
|
||||
elif length < (1 << 16): # Length fits in 2-bytes
|
||||
byte2 |= 126 # Magic code
|
||||
self.sock.write(struct.pack('!BBH', byte1, byte2, length))
|
||||
|
||||
elif length < (1 << 64):
|
||||
byte2 |= 127 # Magic code
|
||||
self.sock.write(struct.pack('!BBQ', byte1, byte2, length))
|
||||
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
if mask: # Mask is 4 bytes
|
||||
mask_bits = struct.pack('!I', random.getrandbits(32))
|
||||
self.sock.write(mask_bits)
|
||||
|
||||
data = bytes(b ^ mask_bits[i % 4]
|
||||
for i, b in enumerate(data))
|
||||
|
||||
self.sock.write(data)
|
||||
|
||||
def recv(self):
|
||||
"""
|
||||
Receive data from the websocket.
|
||||
This is slightly different from 'websockets' in that it doesn't
|
||||
fire off a routine to process frames and put the data in a queue.
|
||||
If you don't call recv() sufficiently often you won't process control
|
||||
frames.
|
||||
"""
|
||||
assert self.open
|
||||
|
||||
while self.open:
|
||||
try:
|
||||
fin, opcode, data = self.read_frame()
|
||||
except NoDataException:
|
||||
return ''
|
||||
except ValueError:
|
||||
LOGGER.debug("Failed to read frame. Socket dead.")
|
||||
self._close()
|
||||
raise ConnectionClosed()
|
||||
|
||||
if not fin:
|
||||
raise NotImplementedError()
|
||||
|
||||
if opcode == OP_TEXT:
|
||||
return data.decode('utf-8')
|
||||
elif opcode == OP_BYTES:
|
||||
return data
|
||||
elif opcode == OP_CLOSE:
|
||||
self._close()
|
||||
return
|
||||
elif opcode == OP_PONG:
|
||||
# Ignore this frame, keep waiting for a data frame
|
||||
continue
|
||||
elif opcode == OP_PING:
|
||||
# We need to send a pong frame
|
||||
if __debug__: LOGGER.debug("Sending PONG")
|
||||
self.write_frame(OP_PONG, data)
|
||||
# And then wait to receive
|
||||
continue
|
||||
elif opcode == OP_CONT:
|
||||
# This is a continuation of a previous frame
|
||||
raise NotImplementedError(opcode)
|
||||
else:
|
||||
raise ValueError(opcode)
|
||||
|
||||
def send(self, buf):
|
||||
"""Send data to the websocket."""
|
||||
|
||||
assert self.open
|
||||
|
||||
if isinstance(buf, str):
|
||||
opcode = OP_TEXT
|
||||
buf = buf.encode('utf-8')
|
||||
elif isinstance(buf, bytes):
|
||||
opcode = OP_BYTES
|
||||
else:
|
||||
raise TypeError()
|
||||
|
||||
self.write_frame(opcode, buf)
|
||||
|
||||
def close(self, code=CLOSE_OK, reason=''):
|
||||
"""Close the websocket."""
|
||||
if not self.open:
|
||||
return
|
||||
|
||||
buf = struct.pack('!H', code) + reason.encode('utf-8')
|
||||
|
||||
self.write_frame(OP_CLOSE, buf)
|
||||
self._close()
|
||||
|
||||
def _close(self):
|
||||
if __debug__: LOGGER.debug("Connection closed")
|
||||
self.open = False
|
||||
self.sock.close()
|
29
main.py
Normal file
29
main.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import ujson
|
||||
import utime as time
|
||||
from uwebsockets import client
|
||||
|
||||
HOST="ws://10.64.12.20:8100"
|
||||
NODEID="esp8266-1234"
|
||||
NAME="esp8266-1234"
|
||||
|
||||
def register(ws):
|
||||
ret = { "command": "register", "nodeid": NODEID, "name": NAME, "ts": time.time()}
|
||||
ws.send(ujson.dumps(ret))
|
||||
|
||||
def listen(ws):
|
||||
while (True):
|
||||
msg = ws.recv()
|
||||
if msg:
|
||||
print("< {}".format(msg))
|
||||
|
||||
def main():
|
||||
ws = client.connect(HOST)
|
||||
while (True):
|
||||
print("register...")
|
||||
register(ws)
|
||||
print("listen...")
|
||||
listen(ws)
|
||||
time.sleep(5)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue