WIP
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
__pycache__
|
||||
*~
|
||||
|
181
cassini.py
181
cassini.py
@@ -9,6 +9,7 @@ import asyncio
|
||||
import logging
|
||||
import random
|
||||
from simple_mqtt_server import SimpleMQTTServer
|
||||
from simple_http_server import SimpleHTTPServer
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@@ -18,11 +19,12 @@ logging.basicConfig(
|
||||
|
||||
SATURN_BROADCAST_PORT = 3000
|
||||
|
||||
SATURN_STATUS_PRINTING = 4
|
||||
SATURN_STATUS_COMPLETE = 16 # ??
|
||||
|
||||
SATURN_CMD_0 = 0 # null data
|
||||
SATURN_CMD_1 = 1 # null data
|
||||
SATURN_CMD_SET_REPORT_TIME_PERIOD = 512
|
||||
SATURN_CMD_SET_MYSTERY_TIME_PERIOD = 512 # "TimePeriod": 5000
|
||||
SATURN_CMD_START_PRINTING = 128 # "Filename": "X", "StartLayer": 0
|
||||
SATURN_CMD_UPLOAD_FILE = 256 # "Check": 0, "CleanCache": 1, "Compress": 0, "FileSize": 3541068, "Filename": "_ResinXP2-ValidationMatrix_v2.goo", "MD5": "205abc8fab0762ad2b0ee1f6b63b1750", "URL": "http://${ipaddr}:58883/f60c0718c8144b0db48b7149d4d85390.goo" },
|
||||
SATURN_CMD_DISCONNECT = 64 # Maybe disconnect?
|
||||
@@ -36,115 +38,118 @@ def handle_exception(loop, context):
|
||||
name = context.get("future").get_coro().__name__
|
||||
logging.error(f"Caught exception from {name}: {msg}")
|
||||
|
||||
def find_printers_on(iface):
|
||||
#netaddr = netifaces.ifaddresses(iface)
|
||||
#if not netaddr.has_key(netifaces.AF_INET):
|
||||
# print('No IPv4 address found for interface {}'.format(iface))
|
||||
# sys.exit(1)
|
||||
#broadcast = netaddr[netifaces.AF_INET][0]['broadcast']
|
||||
class SaturnPrinter:
|
||||
def __init__(self, addr, desc):
|
||||
self.addr = addr
|
||||
self.desc = desc
|
||||
|
||||
# create UDP socket and send broadcast
|
||||
printers = []
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
with sock:
|
||||
sock.settimeout(PRINTER_SEARCH_TIMEOUT)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
sock.sendto(b'M99999', ('<broadcast>', SATURN_BROADCAST_PORT))
|
||||
# Class method: UDP broadcast search for all printers
|
||||
def find_printers(timeout=1):
|
||||
printers = []
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
with sock:
|
||||
sock.settimeout(PRINTER_SEARCH_TIMEOUT)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, timeout)
|
||||
sock.sendto(b'M99999', ('<broadcast>', SATURN_BROADCAST_PORT))
|
||||
|
||||
now = time.time()
|
||||
while True:
|
||||
if time.time() - now > PRINTER_SEARCH_TIMEOUT:
|
||||
break
|
||||
try:
|
||||
data, addr = sock.recvfrom(1024)
|
||||
except socket.timeout:
|
||||
continue
|
||||
else:
|
||||
print(f'Found printer at {addr}')
|
||||
pdata = json.loads(data.decode('utf-8'))
|
||||
pdata['addr'] = addr
|
||||
printers.append(pdata)
|
||||
return printers
|
||||
now = time.time()
|
||||
while True:
|
||||
if time.time() - now > timeout:
|
||||
break
|
||||
try:
|
||||
data, addr = sock.recvfrom(1024)
|
||||
except socket.timeout:
|
||||
continue
|
||||
else:
|
||||
print(f'Found printer at {addr}')
|
||||
pdata = json.loads(data.decode('utf-8'))
|
||||
printers.append(SaturnPrinter(addr, pdata))
|
||||
return printers
|
||||
|
||||
# Tell this printer to connect to the given mqtt server
|
||||
def connect(self, mqtt, http):
|
||||
self.mqtt = mqtt
|
||||
self.http = http
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
with sock:
|
||||
sock.sendto(b'M66666 ' + str(mqtt.port).encode('utf-8'), self.addr)
|
||||
|
||||
def describe(self):
|
||||
attrs = self.desc['Data']['Attributes']
|
||||
return f"{attrs['Name']} ({attrs['MachineName']})"
|
||||
|
||||
def connect_printer(printer, srvport):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
with sock:
|
||||
sock.sendto(b'M66666 ' + str(srvport).encode('utf-8'), printer['addr'])
|
||||
def send_command(self, cmdid, data=None):
|
||||
# generate 16-byte random identifier as a hex string
|
||||
hexstr = '%032x' % random.getrandbits(128)
|
||||
timestamp = int(time.time() * 1000)
|
||||
mainboard = self.desc['Data']['Attributes']['MainboardID']
|
||||
cmd_data = {
|
||||
"Data": {
|
||||
"Cmd": cmdid,
|
||||
"Data": data,
|
||||
"From": 0,
|
||||
"MainboardID": mainboard,
|
||||
"RequestID": hexstr,
|
||||
"TimeStamp": timestamp
|
||||
},
|
||||
"Id": self.desc['Id']
|
||||
}
|
||||
print("SENDING REQUEST: " + json.dumps(cmd_data))
|
||||
self.mqtt.outgoing_messages.put_nowait({'topic': '/sdcp/request/' + mainboard, 'payload': json.dumps(cmd_data)})
|
||||
|
||||
async def create_server():
|
||||
async def create_mqtt_server():
|
||||
mqtt = SimpleMQTTServer('0.0.0.0', 0)
|
||||
await mqtt.start()
|
||||
logging.info(f"Server created, port {mqtt.port}")
|
||||
return mqtt
|
||||
logging.info(f"MQTT Server created, port {mqtt.port}")
|
||||
mqtt_server_task = asyncio.create_task(mqtt.serve_forever())
|
||||
return mqtt, mqtt.port, mqtt_server_task
|
||||
|
||||
async def serve_forever(mqtt):
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.set_exception_handler(handle_exception)
|
||||
await mqtt.serve_forever()
|
||||
|
||||
async def find_printers():
|
||||
printers = find_printers_on('en0')
|
||||
if len(printers) == 0:
|
||||
print('No printers found')
|
||||
sys.exit(1)
|
||||
for i, p in enumerate(printers):
|
||||
attrs = p['Data']['Attributes']
|
||||
#print('{}: {} ({})'.format(i, attrs['Name'], attrs['MachineName']))
|
||||
return printers
|
||||
|
||||
async def printer_setup(mqtt_port):
|
||||
PRINTERS = await find_printers()
|
||||
printer = PRINTERS[0]
|
||||
connect_printer(printer, mqtt_port)
|
||||
return printer
|
||||
async def create_http_server():
|
||||
http = SimpleHTTPServer('0.0.0.0', 0)
|
||||
await http.start()
|
||||
logging.info(f"HTTP Server created, port {http.port}")
|
||||
http_server_task = asyncio.create_task(http.serve_forever())
|
||||
return http, http.port, http_server_task
|
||||
|
||||
def print_printer_status(printers):
|
||||
for i, p in enumerate(printers):
|
||||
attrs = p['Data']['Attributes']
|
||||
status = p['Data']['Status']
|
||||
attrs = p.desc['Data']['Attributes']
|
||||
status = p.desc['Data']['Status']
|
||||
printInfo = status['PrintInfo']
|
||||
print(f"{i}: {attrs['Name']} ({attrs['MachineName']})")
|
||||
print(f" Status: {printInfo['Status']} Layers: {printInfo['CurrentLayer']}/{printInfo['TotalLayer']}")
|
||||
|
||||
def send_printer_command(mqtt, printer, cmdid, data=None):
|
||||
# generate 16-byte random identifier as a hex string
|
||||
hexstr = '%032x' % random.getrandbits(128)
|
||||
timestamp = int(time.time() * 1000)
|
||||
mainboard = printer['Data']['Attributes']['MainboardID']
|
||||
cmd_data = {
|
||||
"Data": {
|
||||
"Cmd": cmdid,
|
||||
"Data": json.dumps(data) if data is not None else "null",
|
||||
"From": 0,
|
||||
"MainboardID": mainboard,
|
||||
"RequestID": hexstr,
|
||||
"TimeStamp": timestamp
|
||||
},
|
||||
"Id": printer['Id']
|
||||
}
|
||||
print(json.dumps(cmd_data))
|
||||
mqtt.outgoing_messages.put_nowait({'topic': '/sdcp/request/' + mainboard, 'payload': json.dumps(cmd_data)})
|
||||
|
||||
async def main():
|
||||
cmd = None
|
||||
printers = SaturnPrinter.find_printers()
|
||||
|
||||
if len(printers) == 0:
|
||||
print("No printers found")
|
||||
return
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
cmd = sys.argv[1]
|
||||
|
||||
if cmd == 'status':
|
||||
printers = await find_printers()
|
||||
print_printer_status(printers)
|
||||
else:
|
||||
mqtt = await create_server()
|
||||
server_task = asyncio.create_task(serve_forever(mqtt))
|
||||
printer = await printer_setup(mqtt.port)
|
||||
await asyncio.sleep(3)
|
||||
send_printer_command(mqtt, printer, 0)
|
||||
await asyncio.sleep(1)
|
||||
send_printer_command(mqtt, printer, 1)
|
||||
await asyncio.sleep(1)
|
||||
send_printer_command(mqtt, printer, 512, { "TimePeriod": 5000 })
|
||||
return
|
||||
|
||||
# Spin up our private mqtt server
|
||||
mqtt, mqtt_port, mqtt_task = await create_mqtt_server()
|
||||
http, http_port, http_task = await create_http_server()
|
||||
|
||||
printer = printers[0]
|
||||
printer.connect(mqtt, http)
|
||||
|
||||
await asyncio.sleep(3)
|
||||
printer.send_command(SATURN_CMD_0)
|
||||
await asyncio.sleep(1)
|
||||
printer.send_command(SATURN_CMD_1)
|
||||
await asyncio.sleep(1)
|
||||
printer.send_command(SATURN_CMD_SET_MYSTERY_TIME_PERIOD, { 'TimePeriod': 5 })
|
||||
await asyncio.sleep(1000)
|
||||
|
||||
#printer_task = asyncio.create_task(printer_setup(mqtt.port))
|
||||
await asyncio.sleep(1000)
|
||||
#while True:
|
||||
# if server_task is not None and server_task.done():
|
||||
# print("Server task done")
|
||||
|
67
simple_http_server.py
Normal file
67
simple_http_server.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import asyncio
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
class SimpleHTTPServer:
|
||||
def __init__(self, host="127.0.0.1", port=0):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.server = None
|
||||
self.routes = {}
|
||||
|
||||
def register_file_route(self, path, filename):
|
||||
size = os.path.getsize(filename)
|
||||
md5 = hashlib.md5()
|
||||
with open(filename, 'rb') as f:
|
||||
while True:
|
||||
data = f.read(1024)
|
||||
if not data:
|
||||
break
|
||||
md5.update(data)
|
||||
route = { 'file': filename, 'size': size, 'md5': md5.hexdigest() }
|
||||
self.routes[path] = route
|
||||
return route
|
||||
|
||||
async def start(self):
|
||||
self.server = await asyncio.start_server(self.handle_client, self.host, self.port)
|
||||
self.port = self.server.sockets[0].getsockname()[1]
|
||||
|
||||
async def serve_forever(self):
|
||||
await self.server.serve_forever()
|
||||
|
||||
async def handle_client(self, reader, writer):
|
||||
data = b''
|
||||
while True:
|
||||
data += await reader.read(1024)
|
||||
if b'\r\n\r\n' in data:
|
||||
break
|
||||
|
||||
request_line = data.decode().splitlines()[0]
|
||||
method, path, _ = request_line.split()
|
||||
|
||||
if path not in self.routes:
|
||||
writer.write("HTTP/1.1 404 Not Found\r\n".encode())
|
||||
writer.close()
|
||||
return
|
||||
|
||||
route = self.routes[path]
|
||||
header = f"HTTP/1.1 200 OK\r\n"
|
||||
header += f"Content-Type: application/octet-stream\r\n"
|
||||
header += f"Etag: {route['md5']}\r\n"
|
||||
header += f"Content-Length: {path['size']}\r\n"
|
||||
|
||||
writer.write(header.encode())
|
||||
|
||||
if method == "GET":
|
||||
writer.write(b'\r\n')
|
||||
with open(route['file'], 'rb') as f:
|
||||
while True:
|
||||
data = f.read(8192)
|
||||
if not data:
|
||||
break
|
||||
writer.write(data)
|
||||
|
||||
await writer.drain()
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
@@ -181,6 +181,3 @@ class SimpleMQTTServer:
|
||||
self.next_pack_id_value += 1
|
||||
return pack_id
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = SimpleMQTTServer()
|
||||
asyncio.run(server.run())
|
||||
|
Reference in New Issue
Block a user