533 lines
16 KiB
Python
Executable File
533 lines
16 KiB
Python
Executable File
#!/usr/bin/python3
|
|
# -*- coding: utf-8 -*-
|
|
# -*- mode: Python -*-
|
|
'''
|
|
LJ Laser Server v0.8.2
|
|
|
|
Inspiration for a future WebUI icon menu :
|
|
https://codepen.io/AlbertFeynman/pen/mjXeMV
|
|
|
|
Laser server + webUI servers (ws + OSC)
|
|
|
|
- get point list to draw : /pl/lasernumber
|
|
- for report /lstt/lasernumber /lack/lasernumber /cap/lasernumber
|
|
- a nice ws debug tool : websocat
|
|
- a "plugin" is a generator that send points to LJ. Plugins if they have an open OSC port can be checked and restart if in the same computer.
|
|
|
|
|
|
All used ports:
|
|
|
|
8002 OSC incoming
|
|
9001 Websocket communication with WebGUI
|
|
Plugins OSC Ports (see LJ.conf)
|
|
|
|
'''
|
|
# import pdb
|
|
import traceback
|
|
|
|
from libs3 import log
|
|
|
|
print("")
|
|
print("")
|
|
log.infog("LJ Laser Server")
|
|
log.infog("v0.8.2")
|
|
print("")
|
|
print("-h will display help")
|
|
print("")
|
|
|
|
import redis
|
|
import os
|
|
|
|
ljpath = r'%s' % os.getcwd().replace('\\', '/')
|
|
import sys
|
|
|
|
# sys.path.append('libs3/')
|
|
from libs3 import gstt, settings
|
|
config = settings.config
|
|
gstt.ljpath = ljpath
|
|
|
|
log.info("Reading " + gstt.ConfigName + " setup file...")
|
|
settings.Read()
|
|
|
|
# Arguments may alter .conf file so import settings first then cli
|
|
from libs3 import cli
|
|
|
|
settings.Write()
|
|
|
|
from multiprocessing import Process, set_start_method
|
|
import random, ast
|
|
from libs3 import plugins
|
|
|
|
# from libs3 import lasytracer as tracer
|
|
from libs3 import tracer
|
|
|
|
from libs3 import homographyp, commands, font1
|
|
# import subprocess
|
|
import os
|
|
# import midi
|
|
from libs3 import OSC3
|
|
from websocket_server import WebsocketServer
|
|
# import socket
|
|
import types, _thread, time
|
|
|
|
r = redis.StrictRedis(host=gstt.LjayServerIP, port=6379, db=0)
|
|
# r = redis.StrictRedis(host=gstt.LjayServerIP , port=6379, db=0, password='-+F816Y+-')
|
|
args = [0, 0]
|
|
|
|
|
|
def dac_process(number, pl, dac_family):
|
|
import sys
|
|
from libs3 import gstt
|
|
|
|
print("Starting dac process", number)
|
|
try:
|
|
d = tracer.DAC(number, pl, dac_family=dac_family)
|
|
|
|
while True:
|
|
d.play_stream()
|
|
|
|
except Exception as e:
|
|
|
|
import sys
|
|
import traceback
|
|
|
|
if gstt.debug > 0:
|
|
# if True:
|
|
log.err('\n---------------------')
|
|
log.err('Exception: %s' % e)
|
|
log.err('- - - - - - - - - - -')
|
|
traceback.print_tb(sys.exc_info()[2])
|
|
print("\n")
|
|
pass
|
|
|
|
except KeyboardInterrupt:
|
|
sys.exit(0)
|
|
|
|
|
|
#
|
|
# Servers init variables
|
|
#
|
|
|
|
print("Start Scene number :", gstt.SceneNumber)
|
|
|
|
print("WebUI connect to :", gstt.wwwIP)
|
|
|
|
serverIP = gstt.LjayServerIP
|
|
print("Redis IP :", serverIP)
|
|
|
|
oscserverIP = gstt.oscIPin
|
|
print("OSCserver IP :", oscserverIP)
|
|
|
|
nozoscIP = gstt.nozoscip
|
|
print("Nozosc IP :", nozoscIP)
|
|
|
|
# gstt.debug = 1
|
|
debug = gstt.debug
|
|
print("Debug :", debug)
|
|
|
|
# Websocket listening port
|
|
wsPORT = 9001
|
|
|
|
# oscserver
|
|
# OSC Server : accept OSC message on port 8002
|
|
# oscIPin = "192.168.1.10"s
|
|
oscserverIPin = serverIP
|
|
|
|
print("oscserverIPin", oscserverIPin)
|
|
oscserverPORTin = 8002
|
|
|
|
# OSC Client : to send OSC message to an IP port 8001
|
|
oscserverIPout = oscserverIP
|
|
oscserverPORTout = 8001
|
|
|
|
'''
|
|
# Nozoid OSC Client : to send OSC message to Nozoid inport 8003
|
|
NozoscIPout = nozoscIP
|
|
NozoscPORTout = plugins.Port("nozoid")
|
|
'''
|
|
|
|
''''
|
|
# Planetarium OSC Client : to send OSC message to planetarium inport 8005
|
|
planetIPout = nozoscIP
|
|
planetPORTout = plugins.Port("planet")
|
|
'''
|
|
|
|
import socket
|
|
|
|
# retry = 1
|
|
# delay = 1
|
|
|
|
|
|
#
|
|
# OSC
|
|
#
|
|
|
|
oscserver = OSC3.OSCServer((oscserverIPin, oscserverPORTin))
|
|
oscserver.timeout = 0
|
|
OSCRunning = True
|
|
|
|
|
|
def handle_timeout(self):
|
|
self.timed_out = True
|
|
|
|
|
|
oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver)
|
|
|
|
|
|
# OSC default path handler : send incoming OSC message to UI via websocket 9001
|
|
def handler(path, tags, args, source):
|
|
oscpath = path.split("/")
|
|
if gstt.debug > 0:
|
|
print("")
|
|
print("OSC handler in main said : path", path, " oscpath ", oscpath, " args", args)
|
|
|
|
if oscpath[1] != "pong":
|
|
sendWSall(path + " " + str(args[0]))
|
|
commands.handler(oscpath, args)
|
|
|
|
|
|
# RAW OSC Frame available ?
|
|
def osc_frame():
|
|
# print 'oscframe'
|
|
# clear timed_out flag
|
|
oscserver.timed_out = False
|
|
# handle all pending requests then return
|
|
while not oscserver.timed_out:
|
|
oscserver.handle_request()
|
|
|
|
|
|
def PingAll():
|
|
if gstt.debug > 0:
|
|
print("Pinging all plugins...")
|
|
|
|
for plugin in list(gstt.plugins.keys()):
|
|
if gstt.debug > 0:
|
|
print("pinging", plugin)
|
|
# sendWSall("/"+ plugin + "/start 0")
|
|
plugins.Ping(plugin)
|
|
|
|
|
|
# OSC server Thread : handler, dacs reports and simulator points sender to UI.
|
|
def osc_thread():
|
|
# while True:
|
|
try:
|
|
while True:
|
|
|
|
time.sleep(0.1)
|
|
osc_frame()
|
|
for laserid in range(0, gstt.LaserNumber): # Laser not used -> led is not lit
|
|
|
|
lstate = {'0': 'IDLE', '1': 'PREPARE', '2': "PLAYING", '64': "NOCONNECTION ?"}
|
|
lstt = r.get('/lstt/' + str(laserid)).decode('ascii')
|
|
# print ("laserid", laserid,"lstt",lstt, type(lstt))
|
|
if gstt.debug > 1:
|
|
print("DAC", laserid, "is in (lstt) :", lstt, lstate[str(lstt)])
|
|
if lstt == "0": # Dac IDLE state(0) -> led is blue (3)
|
|
sendWSall("/lstt/" + str(laserid) + " 3")
|
|
|
|
if lstt == "1": # Dac PREPARE state (1) -> led is cyan (2)
|
|
sendWSall("/lstt/" + str(laserid) + " 2")
|
|
|
|
if lstt == "2": # Dac PLAYING (2) -> led is green (1)
|
|
sendWSall("/lstt/" + str(laserid) + " 1")
|
|
|
|
ackstate = {'61': 'ACK', '46': 'FULL', '49': "INVALID", '21': 'STOP', '64': "NOCONNECTION ?",
|
|
'35': "NOCONNECTION ?", '97': 'ACK', '70': 'FULL', '73': "INVALID", '33': 'STOP',
|
|
'100': "NOCONNECTION", '48': "NOCONNECTION", 'a': 'ACK', 'F': 'FULL', 'I': "INVALID",
|
|
'!': 'STOP', 'd': "NOCONNECTION", '0': "NOCONNECTION"}
|
|
lack = r.get('/lack/' + str(laserid)).decode('ascii')
|
|
|
|
if gstt.debug > 1:
|
|
print("DAC", laserid, "answered (lack):", lack, chr(int(lack)), ackstate[str(lack)])
|
|
|
|
if chr(int(lack)) == 'a': # Dac sent ACK ("a") -> led is green (1)
|
|
sendWSall("/lack/" + str(laserid) + " 1")
|
|
|
|
if chr(int(lack)) == 'F': # Dac sent FULL ("F") -> led is orange (5)
|
|
sendWSall("/lack/" + str(laserid) + " 5")
|
|
|
|
if chr(int(lack)) == 'I': # Dac sent INVALID ("I") -> led is yellow (4)
|
|
sendWSall("/lack/" + str(laserid) + " 4")
|
|
# print lack
|
|
|
|
if lack == "64" or lack == "35": # no connection to dac -> leds are red (6)
|
|
sendWSall("/lack/" + str(laserid) + " 6")
|
|
sendWSall("/lstt/" + str(laserid) + " 6")
|
|
# sendWSall("/lstt/" + str(laserid) + " 0")
|
|
sendWSall("/points/" + str(laserid) + " 6")
|
|
|
|
else:
|
|
# last number of points sent to etherdream buffer
|
|
sendWSall("/points/" + str(laserid) + " " + str(r.get('/cap/' + str(laserid)).decode('ascii')))
|
|
|
|
# print("Sending simu frame from",'/pl/'+str(gstt.SceneNumber)+'/'+str(gstt.Laser))
|
|
# print(r.get('/pl/'+str(gstt.SceneNumber)+'/'+str(gstt.Laser)))
|
|
sendWSall(
|
|
"/simul" + " " + str(r.get('/pl/' + str(gstt.SceneNumber) + '/' + str(gstt.Laser)).decode('ascii')))
|
|
if random.randint(0, 100) > 95:
|
|
plugins.sendbroadcast()
|
|
|
|
|
|
except Exception as e:
|
|
import sys, traceback
|
|
print('\n--------------------------')
|
|
print('OSC Thread Exception: %s' % e)
|
|
print('- - - - - - - - - - - - - - ')
|
|
traceback.print_tb(sys.exc_info()[2])
|
|
print("\n")
|
|
|
|
|
|
#
|
|
# Websocket part
|
|
#
|
|
|
|
# Called for every WS client connecting (after handshake)
|
|
def new_client(client, wserver):
|
|
print("New WS client connected and was given id %d" % client['id'])
|
|
sendWSall("/status Hello " + str(client['id']))
|
|
|
|
for laserid in range(0, gstt.LaserNumber):
|
|
|
|
sendWSall("/ip/" + str(laserid) + " " + str(gstt.lasersIPS[laserid]))
|
|
sendWSall("/kpps/" + str(laserid) + " " + str(gstt.kpps[laserid]))
|
|
# sendWSall("/laser"+str(laserid)+"/start 1")
|
|
sendWSall("/laser " + str(laserid))
|
|
# print("/laser "+str(laserid))
|
|
sendWSall("/lack/" + str(laserid) + " 6")
|
|
# print("/lack/" + str(laserid) + " 6")
|
|
sendWSall("/lstt/" + str(laserid) + " 6")
|
|
# print("/lstt/" + str(laserid) + " 6")
|
|
sendWSall("/points/" + str(laserid) + " 0")
|
|
# print("/points/" + str(laserid) + " 0")
|
|
|
|
if gstt.swapX[laserid] == 1:
|
|
sendWSall("/swap/X/" + str(laserid) + " 1")
|
|
else:
|
|
sendWSall("/swap/X/" + str(laserid) + " 0")
|
|
|
|
if gstt.swapY[laserid] == 1:
|
|
sendWSall("/swap/Y/" + str(laserid) + " 1")
|
|
else:
|
|
sendWSall("/swap/Y/" + str(laserid) + " 0")
|
|
|
|
|
|
# Called for every WS client disconnecting
|
|
def client_left(client, wserver):
|
|
print("WS Client(%d) disconnected" % client['id'])
|
|
|
|
|
|
# Called for each WS received message.
|
|
def message_received(client, wserver, message):
|
|
# if len(message) > 200:
|
|
# message = message[:200]+'..'
|
|
|
|
# if gstt.debug >0:
|
|
# print ("")
|
|
# print("WS Client(%d) said: %s" % (client['id'], message))
|
|
|
|
oscpath = message.split(" ")
|
|
# print "WS Client", client['id'], "said :", message, "splitted in an oscpath :", oscpath
|
|
if gstt.debug > 0:
|
|
print("WS Client", client['id'], "said :", message, "splitted in an oscpath :", oscpath)
|
|
|
|
PingAll()
|
|
message4plugin = False
|
|
|
|
# WS received Message is for a plugin ?
|
|
|
|
for plugin in list(gstt.plugins.keys()):
|
|
|
|
if oscpath[0].find(plugin) != -1:
|
|
|
|
message4plugin = True
|
|
# print(oscpath)
|
|
if plugins.Send(plugin, oscpath):
|
|
print("plugins sent incoming WS correctly to", plugin)
|
|
else:
|
|
print("plugins detected", plugin, "offline.")
|
|
|
|
# WS received message is an LJ command
|
|
|
|
if message4plugin == False:
|
|
|
|
if len(oscpath) == 1:
|
|
args[0] = "noargs"
|
|
# print "noargs command"
|
|
|
|
elif len(oscpath) > 1:
|
|
args[0] = str(oscpath[1])
|
|
# print "arg",oscpath[1]
|
|
|
|
commands.handler(oscpath[0].split("/"), args)
|
|
|
|
# if needed a loop back : WS Client -> server -> WS Client
|
|
# sendWSall("ws"+message)
|
|
|
|
|
|
def handle_timeout(self):
|
|
self.timed_out = True
|
|
|
|
|
|
def sendWSall(message):
|
|
# if gstt.debug >0:
|
|
# print("WS sending %s" % (message))
|
|
wserver.send_message_to_all(message)
|
|
|
|
|
|
'''
|
|
print ""
|
|
print "Midi Configuration"
|
|
midi.InConfig()
|
|
midi.OutConfig()
|
|
'''
|
|
|
|
|
|
def fff(name):
|
|
print()
|
|
print('HELLO', name) # indent
|
|
print()
|
|
|
|
|
|
#
|
|
# Create a startup point list for each laser : 0,1,2,...
|
|
#
|
|
|
|
print("")
|
|
log.info("Creating startup point lists...")
|
|
|
|
if r.set("/clientkey", "/pl/" + str(gstt.SceneNumber) + "/") == True:
|
|
print("sent clientkey : /pl/" + str(gstt.SceneNumber) + "/")
|
|
|
|
# pdb.set_trace()
|
|
for sceneid in range(0, gstt.MaxScenes + 1):
|
|
print("Scene " + str(sceneid))
|
|
# digit_points = font1.DigitsDots(sceneid,65280)
|
|
|
|
# Order all lasers to show its number at startup -> tell all 4 laser process to USER PLs
|
|
for laserid in range(0, gstt.LaserNumber):
|
|
|
|
digit_points = font1.DigitsDots(laserid, 65280)
|
|
if r.set('/pl/' + str(sceneid) + '/' + str(laserid), str(digit_points)) == True:
|
|
pass
|
|
# print( ast.literal_eval(r.get('/pl/'+str(sceneid)+'/'+str(laserid)).decode('ascii')))
|
|
# print("/pl/"+str(sceneid)+"/"+str(laserid)+" "+str(ast.literal_eval(r.get('/pl/'+str(sceneid)+'/'+str(laserid)).decode('ascii'))))
|
|
|
|
r.set('/order/' + str(laserid), 0)
|
|
|
|
#
|
|
# Starts one DAC process per requested Laser
|
|
#
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# Bug in 3.8.4 MacOS default multiprocessing start method is spawn. Spawn doesn't work properly
|
|
set_start_method('fork')
|
|
|
|
print("gstt.LaserNumber", gstt.LaserNumber)
|
|
if gstt.LaserNumber == -1:
|
|
log.infog("Autodetected DACs mode")
|
|
commands.DAChecks()
|
|
print("dacs", gstt.dacs)
|
|
|
|
else:
|
|
log.infog("Resquested DACs mode")
|
|
|
|
lasernumber = gstt.LaserNumber - 1
|
|
print("LaserNumber = ", gstt.LaserNumber)
|
|
|
|
log.info("Starting " + str(gstt.LaserNumber) + " DACs process...")
|
|
|
|
# Launch one process (a tracer3 instance) for etherdream / helios
|
|
dac_family = None
|
|
if config["laser0"].get("dac_family"):
|
|
dac_family = config["laser0"]["dac_family"]
|
|
dac_worker0 = Process(target=dac_process, args=(0, 0, dac_family))
|
|
dac_worker0.start()
|
|
commands.worker0 = dac_worker0
|
|
print("Tracer 0 : name", dac_worker0.name, "pid", dac_worker0.pid)
|
|
|
|
if lasernumber > 0:
|
|
dac_worker1 = Process(target=dac_process, args=(1, 0,))
|
|
commands.worker1 = dac_worker1
|
|
print("Tracer 1 : name", dac_worker1.name, "pid", dac_worker1.pid)
|
|
dac_worker1.start()
|
|
|
|
if lasernumber > 1:
|
|
dac_worker2 = Process(target=dac_process, args=(2, 0,))
|
|
dac_worker2.start()
|
|
commands.worker2 = dac_worker2
|
|
print("Tracer 2 : name", dac_worker2.name, "pid", dac_worker2.pid)
|
|
|
|
if lasernumber > 2:
|
|
dac_worker3 = Process(target=dac_process, args=(3, 0,))
|
|
print("Tracer 3 : name", dac_worker3.name, "pid", dac_worker3.pid)
|
|
commands.worker3 = dac_worker3
|
|
dac_worker3.start()
|
|
print("")
|
|
|
|
#
|
|
# start WS and OSC servers
|
|
#
|
|
|
|
try:
|
|
|
|
# Websocket startup
|
|
wserver = WebsocketServer(wsPORT, host=serverIP)
|
|
plugins.Init(wserver)
|
|
|
|
log.info("Starting servers...")
|
|
# Launch OSC thread listening to oscserver
|
|
# print("Launching OSC server...")
|
|
# print("at", oscserverIPin, "port", str(oscserverPORTin))
|
|
# oscserver.addMsgHandler("/noteon", commands.NoteOn)
|
|
# oscserver.addMsgHandler("/scim", commands.Scim)
|
|
# oscserver.addMsgHandler("/line1", commands.Line1)
|
|
# oscserver.addMsgHandler("/forwardui", commands.ForwardUI)
|
|
# # Default OSC handler for all OSC incoming message
|
|
# oscserver.addMsgHandler("default", handler)
|
|
# _thread.start_new_thread(osc_thread, ())
|
|
|
|
print("Launching webUI Websocket server...")
|
|
print("at", serverIP, "port", wsPORT)
|
|
wserver.set_fn_new_client(new_client)
|
|
wserver.set_fn_client_left(client_left)
|
|
wserver.set_fn_message_received(message_received)
|
|
print("")
|
|
log.info("Resetting all Homographies...")
|
|
for laserid in range(0, gstt.LaserNumber):
|
|
homographyp.newEDH(laserid)
|
|
|
|
# plugins autostart
|
|
print("")
|
|
log.info("Plugins startup...")
|
|
|
|
if gstt.autostart != "":
|
|
|
|
for pluginname in gstt.autostart.split(","):
|
|
print("Autostarting", pluginname, "...")
|
|
plugins.Start(pluginname)
|
|
|
|
print("")
|
|
log.infog("LJ server running...")
|
|
|
|
# websocket loop
|
|
|
|
wserver.run_forever()
|
|
|
|
|
|
except Exception:
|
|
log.err("Exception")
|
|
traceback.print_exc()
|
|
|
|
# Gently stop on CTRL C
|
|
|
|
finally:
|
|
|
|
commands.LJautokill()
|
|
|
|
'''
|
|
Some code previously used, for reference :
|
|
|
|
random_points = [(300.0+random.randint(-100, 100), 200.0+random.randint(-100, 100), 0), (500.0+random.randint(-100, 100), 200.0+random.randint(-100, 100), 65280), (500.0+random.randint(-100, 100), 400.0+random.randint(-100, 100), 65280), (300.0+random.randint(-100, 100), 400.0+random.randint(-100, 100), 65280), (300.0+random.randint(-100, 100), 200.0+random.randint(-100, 100), 65280)]
|
|
'''
|