418 lines
12 KiB
Python
Executable File
418 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import get_specs
|
|
import traceback
|
|
#import logging
|
|
import yaml
|
|
from multiprocessing import Process, Manager, Pool, TimeoutError, active_children, log_to_stderr, Pipe, Queue
|
|
from multiprocessing.pool import Pool
|
|
import multiprocessing
|
|
from time import sleep
|
|
from util import fprint
|
|
from util import run_cmd
|
|
import sys
|
|
import ur5_control
|
|
import os
|
|
import signal
|
|
import socket
|
|
from flask import Flask, render_template, request
|
|
import requests
|
|
import led_control
|
|
import server
|
|
import asyncio
|
|
import json
|
|
import process_video
|
|
|
|
|
|
|
|
config = None
|
|
keeprunning = True
|
|
arm_ready = False
|
|
led_ready = False
|
|
camera_ready = False
|
|
sensor_ready = False
|
|
vm_ready = False
|
|
killme = None
|
|
#pool = None
|
|
serverproc = None
|
|
camera = None
|
|
|
|
to_server_queue = Queue()
|
|
from_server_queue = Queue()
|
|
|
|
def arm_start_callback(res):
|
|
global arm_ready
|
|
arm_ready = True
|
|
|
|
def led_start_callback(res):
|
|
global led_ready
|
|
led_ready = True
|
|
|
|
def camera_start_callback(res):
|
|
global camera_ready
|
|
camera_ready = True
|
|
|
|
def sensor_start_callback(res):
|
|
global sensor_ready
|
|
sensor_ready = True
|
|
|
|
def vm_start_callback(res):
|
|
global vm_ready
|
|
vm_ready = True
|
|
|
|
def wait_for(val, name):
|
|
#global val
|
|
if val is False:
|
|
fprint("waiting for " + name + " to complete...")
|
|
while val is False:
|
|
sleep(0.1)
|
|
|
|
def start_server_socket():
|
|
"""app = Flask(__name__)
|
|
|
|
@app.route('/report_ip', methods=['POST'])
|
|
def report_ip():
|
|
client_ip = request.json.get('ip')
|
|
fprint(f"Received IP: {client_ip}")
|
|
# You can store or process the IP address as needed
|
|
return "IP Received", 200
|
|
|
|
app.run(host='0.0.0.0', port=5000)"""
|
|
global to_server_queue
|
|
global from_server_queue
|
|
fprint("Starting WebSocket server...")
|
|
websocket_process = server.start_websocket_server(to_server_queue, from_server_queue)
|
|
|
|
# Example
|
|
#to_server_queue.put("Hello, WebSocket clients!")
|
|
|
|
|
|
while True:
|
|
#print("HI")
|
|
if not from_server_queue.empty():
|
|
client_id, message = from_server_queue.get()
|
|
fprint(f"Message from client {client_id}: {message}")
|
|
|
|
# Message handler
|
|
try:
|
|
decoded = json.loads(message)
|
|
if "type" not in decoded:
|
|
fprint("Missing \"type\" field.")
|
|
continue
|
|
if "call" not in decoded:
|
|
fprint("Missing \"call\" field.")
|
|
continue
|
|
if "data" not in decoded:
|
|
fprint("Missing \"data\" field.")
|
|
continue
|
|
|
|
# if we get here, we have a "valid" data packet
|
|
data = decoded["data"]
|
|
call = decoded["call"]
|
|
match decoded["type"]:
|
|
case "log":
|
|
fprint("log message")
|
|
if call == "send":
|
|
fprint("webapp: " + str(data), sendqueue=to_server_queue)
|
|
elif call == "request":
|
|
fprint("")
|
|
|
|
case "cable_map":
|
|
fprint("cable_map message")
|
|
if call == "send":
|
|
fprint("")
|
|
elif call == "request":
|
|
fprint("")
|
|
|
|
# Lucas' notes
|
|
# Add a ping pong :) response/handler
|
|
# Add a get cable response/handler
|
|
# this will tell the robot arm to move
|
|
# Call for turning off everything
|
|
|
|
# TODO Helper for converting Python Dictionaries to JSON
|
|
# make function: pythonData --> { { "type": "...", "call": "...", "data": pythonData } }
|
|
|
|
case "cable_details":
|
|
fprint("cable_details message")
|
|
if call == "send":
|
|
fprint("")
|
|
elif call == "request":
|
|
fprint("")
|
|
|
|
case "cable_search":
|
|
fprint("cable_search message")
|
|
if call == "send":
|
|
fprint("")
|
|
elif call == "request":
|
|
fprint("")
|
|
|
|
case "keyboard":
|
|
fprint("keyboard message")
|
|
if call == "send":
|
|
fprint("")
|
|
elif call == "request":
|
|
fprint("")
|
|
if data["enabled"] == True:
|
|
# todo : send this to client
|
|
p = Process(target=run_cmd, args=("./keyboard-up.ps1",))
|
|
p.start()
|
|
elif data["enabled"] == False:
|
|
p = Process(target=run_cmd, args=("./keyboard-down.ps1",))
|
|
p.start()
|
|
|
|
case "machine_settings":
|
|
fprint("machine_settings message")
|
|
if call == "send":
|
|
fprint("")
|
|
elif call == "request":
|
|
fprint("")
|
|
|
|
case _:
|
|
fprint("Unknown/unimplemented data type: " + decoded["type"])
|
|
|
|
|
|
except:
|
|
fprint("Non-JSON message recieved")
|
|
continue
|
|
|
|
|
|
sleep(0.001) # Sleep to prevent tight loop
|
|
|
|
|
|
|
|
def start_client_socket():
|
|
app = Flask(__name__)
|
|
|
|
@app.route('/control_client', methods=['POST'])
|
|
def message_from_server():
|
|
# Handle message from server
|
|
data = request.json
|
|
fprint(f"Message from server: {data.get('message')}")
|
|
return "Message received", 200
|
|
|
|
app.run(host='0.0.0.0', port=6000)
|
|
|
|
|
|
def check_server_online(serverip, clientip):
|
|
def send_ip_to_server(server_url, client_ip):
|
|
try:
|
|
response = requests.post(server_url, json={'ip': client_ip}, timeout=1)
|
|
fprint(f"Server response: {response.text}")
|
|
return True
|
|
except requests.exceptions.RequestException as e:
|
|
fprint(f"Error sending IP to server: {e}")
|
|
return False
|
|
|
|
server_url = 'http://' + serverip + ':5000/report_ip'
|
|
while not send_ip_to_server(server_url, clientip):
|
|
sleep(1)
|
|
|
|
fprint("Successfully connected to server.")
|
|
return True
|
|
|
|
|
|
def setup_server(pool):
|
|
# linux server setup
|
|
global config
|
|
global counter
|
|
global sensor_ready
|
|
global camera_ready
|
|
global led_ready
|
|
global arm_ready
|
|
global serverproc
|
|
global camera
|
|
|
|
pool.apply_async(ur5_control.init, (config["arm"]["ip"],), callback=arm_start_callback)
|
|
pool.apply_async(led_control.init, callback=led_start_callback)
|
|
#pool.apply_async(sensor_control.init, callback=sensor_start_callback)
|
|
serverproc = Process(target=start_server_socket)
|
|
serverproc.start()
|
|
|
|
if led_ready is False:
|
|
fprint("waiting for " + "LED controller initialization" + " to complete...", sendqueue=to_server_queue)
|
|
while led_ready is False:
|
|
sleep(0.1)
|
|
fprint("LED controllers initialized.", sendqueue=to_server_queue)
|
|
#to_server_queue.put("[log] LED controllers initialized.")
|
|
sensor_ready = True
|
|
if sensor_ready is False:
|
|
fprint("waiting for " + "Sensor Initialization" + " to complete...", sendqueue=to_server_queue)
|
|
while sensor_ready is False:
|
|
sleep(0.1)
|
|
fprint("Sensors initialized.", sendqueue=to_server_queue)
|
|
|
|
if camera_ready is False:
|
|
fprint("waiting for " + "Camera initilization" + " to complete...", sendqueue=to_server_queue)
|
|
# camera = process_video.qr_reader(config["cameras"]["banner"]["ip"], config["cameras"]["banner"]["port"])
|
|
|
|
fprint("Camera initialized.", sendqueue=to_server_queue)
|
|
|
|
arm_ready = True
|
|
if arm_ready is False:
|
|
fprint("waiting for " + "UR5 initilization" + " to complete...", sendqueue=to_server_queue)
|
|
while arm_ready is False:
|
|
sleep(0.1)
|
|
fprint("Arm initialized.", sendqueue=to_server_queue)
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def mainloop_server(pool):
|
|
global config
|
|
global counter
|
|
global killme
|
|
|
|
if killme.value > 0:
|
|
killall()
|
|
counter = counter + 1
|
|
|
|
# fprint("Looking for QR code...")
|
|
# print(camera.read_qr(30))
|
|
|
|
def run_loading_app():
|
|
|
|
app = Flask(__name__)
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
app.run(debug=True, use_reloader=False, port=7000)
|
|
|
|
def setup_client(pool):
|
|
# Windows client setup
|
|
fprint("Opening browser...")
|
|
firefox = webdriver.Firefox()
|
|
firefox.fullscreen_window()
|
|
global config
|
|
global vm_ready
|
|
global serverproc
|
|
# Open loading wepage
|
|
p = Process(target=run_loading_app)
|
|
p.start()
|
|
|
|
|
|
firefox.get('http://localhost:7000')
|
|
|
|
# start Linux server VM
|
|
if config["core"]["server"] == "Hyper-V":
|
|
run_cmd("Start-VM -Name Jukebox*") # any and all VMs starting with "Jukebox"
|
|
|
|
# Wait for VM to start and be reachable over the network
|
|
serverproc = Process(target=start_client_socket)
|
|
serverproc.start()
|
|
|
|
pool.apply_async(check_server_online, (config["core"]["serverip"],config["core"]["clientip"]), callback=vm_start_callback)
|
|
|
|
#wait_for(vm_ready, "VM Startup")
|
|
|
|
#global vm_ready
|
|
if vm_ready is False:
|
|
fprint("waiting for " + "VM Startup" + " to complete...")
|
|
while vm_ready is False:
|
|
sleep(0.1)
|
|
p.terminate()
|
|
firefox.get("http://" + config["core"]["serverip"] + ":8000")
|
|
return True
|
|
|
|
|
|
def mainloop_client(pool):
|
|
sleep(0.1)
|
|
|
|
# listen for & act on commands from VM, if needed
|
|
# mainly just shut down, possibly connect to wifi or something
|
|
|
|
"""class Logger(object):
|
|
def __init__(self, filename="output.log"):
|
|
self.log = open(filename, "a")
|
|
self.terminal = sys.stdout
|
|
|
|
def write(self, message):
|
|
self.log.write(message)
|
|
#close(filename)
|
|
#self.log = open(filename, "a")
|
|
try:
|
|
self.terminal.write(message)
|
|
except:
|
|
sleep(0)
|
|
|
|
def flush(self):
|
|
print("",end="")"""
|
|
|
|
def killall():
|
|
procs = active_children()
|
|
for proc in procs:
|
|
proc.kill()
|
|
fprint("All child processes killed")
|
|
os.kill(os.getpid(), 9) # dirty kill of self
|
|
|
|
|
|
def killall_signal(a, b):
|
|
global config
|
|
if config["core"]["server"] == "Hyper-V":
|
|
run_cmd("Stop-VM -Name Jukebox*") # any and all VMs starting with "Jukebox"
|
|
killall()
|
|
|
|
def error(msg, *args):
|
|
return multiprocessing.get_logger().error(msg, *args)
|
|
|
|
class LogExceptions(object):
|
|
def __init__(self, callable):
|
|
self.__callable = callable
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
try:
|
|
result = self.__callable(*args, **kwargs)
|
|
|
|
except Exception as e:
|
|
# Here we add some debugging help. If multiprocessing's
|
|
# debugging is on, it will arrange to log the traceback
|
|
error(traceback.format_exc())
|
|
# Re-raise the original exception so the Pool worker can
|
|
# clean up
|
|
raise
|
|
|
|
# It was fine, give a normal answer
|
|
return result
|
|
|
|
class LoggingPool(Pool):
|
|
def apply_async(self, func, args=(), kwds={}, callback=None):
|
|
return Pool.apply_async(self, LogExceptions(func), args, kwds, callback)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
#sys.stdout = Logger(filename="output.log")
|
|
#sys.stderr = Logger(filename="output.log")
|
|
#log_to_stderr(logging.DEBUG)
|
|
fprint("Starting Jukebox control system...")
|
|
with open('config.yml', 'r') as fileread:
|
|
#global config
|
|
config = yaml.safe_load(fileread)
|
|
|
|
with Manager() as manager:
|
|
fprint("Spawning threads...")
|
|
pool = LoggingPool(processes=10)
|
|
counter = 0
|
|
killme = manager.Value('d', 0)
|
|
signal.signal(signal.SIGINT, killall_signal)
|
|
if config["core"]["mode"] == "winclient":
|
|
fprint("Starting in client mode.")
|
|
from selenium import webdriver
|
|
if setup_client(pool):
|
|
fprint("Entering main loop...")
|
|
while(keeprunning):
|
|
mainloop_client(pool)
|
|
|
|
elif config["core"]["mode"] == "linuxserver":
|
|
fprint("Starting in server mode.")
|
|
if setup_server(pool):
|
|
fprint("Entering main loop...")
|
|
while(keeprunning):
|
|
mainloop_server(pool)
|
|
|
|
|