Laser process command system and bugfixs.

This commit is contained in:
nrhck 2018-12-14 12:34:41 +01:00
parent 3d3a9b9be4
commit 12e62bb2f0
9 changed files with 206 additions and 222 deletions

View File

@ -5,7 +5,6 @@ By Sam Neurohack, Loloster, Cocoa
LICENCE : CC BY
![LJ](http://www.teamlaser.fr/thsf/images/fulls/THSF9-33.jpg)
A software server with gui for up to 4 lasers live actions. Think creative like Laser "battles", planetarium,...
@ -16,9 +15,10 @@ Needs at least : an etherdream DAC connected to an ILDA laser, RJ 45 IP network
Nozosc : Semi modular synthetizers from Nozoids can send a lot of their inner sound curves and be displayed in many ways, i.e VCO 1 on X axis and LFO 2 on Y axis.
The server approach is based on redis. One process per etherdream is spawn to : retrieve the given point list from redis, warp, resample and manage the given etherdream DAC dialog.
LJ supports Linux and OS X. Windows is unkown but welcome, if someone want to jump in and care about it.
#
# Features among many others.
@ -30,7 +30,7 @@ The server approach is based on redis. One process per etherdream is spawn to :
- Interactive (mouse style) warp correction for each laser.
- Web ui : In your browser open webui/index.html. Javascript is needed.
- Status every 0.5 seconds : every etherdream DAC state, number of buffer points sent,...
- "Optimisation" points automatically added, can be changed live for glitch art. See
- "Optimisation" points automatically added, can be changed live for glitch art. Search "resampler" commands.
#
@ -46,10 +46,32 @@ The server approach is based on redis. One process per etherdream is spawn to :
#
You need to update mainy.conf to your network/etherdreams IPs and Be sure to check command arguments : python mainyservers.py --help
LJ is meant for Live, so a lot of parameters can be changed via OSC/midi, webUI,...
This is *critical and flickering reason #1* if not managed properly : use static network configuration, especially if you move your gear for different venues.
Our "always working solution" :
Our Etherdreams controllers have static IPs defined in their SDcard from 192.168.1.1 to 192.168.1.9. Because wifi will always finally sucks for many reasons, our computers are *gigabits wired connected* with 192.168.1.10 and after. Don't trust end user gear marketing on wifi.
We have a big *laser dedicated gigabit switch*. We provide Internet through wifi on a different network address like 192.168.2.x
Even if etherdreams are 100 Mbits, use gigabits gear. Use gigabits gear. USE GIGABITS GEAR :)
By default LJ uses on 127.0.0.1 (localhost) :
- A websocket on port 9001 for WebUI interaction.
- The redis server on port 6379 ('ljayserverip')
- An OSC server on port 8002. Incoming commands are transfered to webUI.
- An OSC client on 'bhoroscIP' port 8001.
- An OSC client for Nozoids support on 'nozoscIP', port 8003.
You need to update mainy.conf to your network/etherdreams IPs and be sure to check command arguments : python mainyservers.py --help
A dedicated computer to act as "laser server" usually depends on how many lasers you want to control and your main computer load. If you seen flickering with small point lists, try the dedicated computer option.
Program your own "Client" :
-------------------------
@ -65,10 +87,19 @@ Program your own "Client" :
# Install
#
In terminal type :
With Linux, type in a terminal window :
./install.sh
For OS X, you need brew already installed, then :
brew update
brew upgrade
brew install redis
type all install.sh commands beginning line 4.
For Linux and OS X :
Check the bind line in /etc/redis/redis.conf :
- If client and laser servers computers are the same, use 127.0.0.1

17
cli.py
View File

@ -15,13 +15,13 @@ from /team/laser
import gstt
import argparse
print "-h will display help"
print ""
def handle():
if gstt.debug > 2:
print ""
print "Arguments parsing if needed..."
#have to be done before importing bhorosc.py to get correct port assignment
#have to be done before importing bhorosc.py to get correct port assignment
argsparser = argparse.ArgumentParser(description="LJay")
argsparser.add_argument("-r","--redisIP",help="Redis computer IP address (gstt.LjayServerIP by default)",type=str)
argsparser.add_argument("-i","--iport",help="OSC port number to listen to (8001 by default)",type=int)
@ -45,6 +45,10 @@ def handle():
args = argsparser.parse_args()
# Verbose = debug
if args.verbose != None:
#print "setting gstt.debug to", args.verbose
gstt.debug = args.verbose
# Ports arguments
if args.iport:
@ -111,9 +115,7 @@ def handle():
# Verbose = debug
if args.verbose != None:
gstt.debug = args.verbose
# Lasers = number of laser connected
@ -161,3 +163,4 @@ def handle():
gstt.swapy[0] = 1
#Settings.Write()
handle()

View File

@ -8,7 +8,7 @@ Etat global (anciennement singleton de la classe GameState + autres VARIABLES n
#ConfigName = "setexample.conf"
ConfigName = "mainy.conf"
debug = 0
debug = 2
anims= [[],[],[],[]]

17
install.sh Normal file
View File

@ -0,0 +1,17 @@
#!/bin/bash
sudo apt install python-pip
sudo apt install git
pip uninstall serial
pip uninstall rtmidi
pip install libasound2-dev python-dev libpython-dev libjack-dev
pip install pysimpledmx
pip install numpy
pip install scipy
pip install mido
pip install python-rtmidi
git clone https://github.com/ptone/pyosc --depth 1 /tmp/pyosc && cd /tmp/pyosc && sudo ./setup.py install
pip install pyserial
pip install redis

79
las.py
View File

@ -24,65 +24,68 @@ import redis
r = redis.StrictRedis(host=gstt.LjayServerIP , port=6379, db=0)
def GridOn(laser):
print "Grid for laser ", laser
# Grid PL is Laser bit 0 = 1 and bit 1 = 1
order = r.get('/order')
neworder = order | (1<<laser*2)
neworder = neworder | (1<< 1+laser*2)
r.set('/order', str(neworder))
def UserOn(laser):
print "User for laser ", laser
r.set('/order/'+str(laser), 0)
# Laser bit 0 = 0 and bit 1 = 0 : USER PL
order = r.get('/order')
neworder = order & ~(1<< laser*2)
neworder = neworder & ~(1<< 1+ laser*2)
r.set('/order', str(neworder))
def BlackOn(laser):
print "Black for laser ", laser
# Black PL is Laser bit 0 = 1 and bit 1 = 0 :
order = r.get('/order')
neworder = order | (1<<laser*2)
neworder = neworder & ~(1<< 1+laser*2)
r.set('/order', str(neworder))
#order = r.get('/order')
#neworder = order & ~(1<< laser*2)
#neworder = neworder & ~(1<< 1+ laser*2)
#r.set('/order', str(neworder))
def NewEDH(laser):
settings.Write()
print "New EDH for laser ", laser
r.set('/order/'+str(laser), 1)
# Laser bit 0 = 0 and bit 1 = 1 : New EDH
order = r.get('/order')
neworder = order & ~(1<< laser*2)
neworder = neworder | (1<< 1+laser*2)
r.set('/order', str(neworder))
#order = r.get('/order')
#neworder = order & ~(1<< laser*2)
#neworder = neworder | (1<< 1+laser*2)
#r.set('/order', str(neworder))
def BlackOn(laser):
def handler(path, tags, args, source):
print "Black for laser ", laser
r.set('/order/'+str(laser), 2)
# Black PL is Laser bit 0 = 1 and bit 1 = 0 :
#order = r.get('/order')
#neworder = order | (1<<laser*2)
#neworder = neworder & ~(1<< 1+laser*2)
oscpath = path.split("/")
def GridOn(laser):
print "Grid for laser ", laser
r.set('/order/'+str(laser), 3)
# Grid PL is Laser bit 0 = 1 and bit 1 = 1
#order = r.get('/order')
#neworder = order | (1<<laser*2)
#neworder = neworder | (1<< 1+laser*2)
#r.set('/order', str(neworder))
def Resampler(laser):
print "Resampler change for laser ", laser
r.set('/order/'+str(laser), 4)
def handler(oscpath, args):
pathlength = len(oscpath)
sendWSall(path + " " + str(args[0]))
if pathlength == 2:
laser = int(oscpath[2])
else:
laser = int(oscpath[2])
if debug >0:
print ""
print "default handler"
print "Bhorosc said for laser",laser,": ", path, oscpath, args
# /grid/lasernumber value (0 or 1)
if oscpath[1] == "grid":

View File

@ -1,7 +1,8 @@
[General]
set = 5
curve = 0
lasernumber = 3
lasernumber = 1
debug = 0
ljayserverip = 127.0.0.1
nozoscip = 127.0.0.1
bhoroscip = 127.0.0.1
@ -40,9 +41,9 @@ finangle = 0.0
swapx = 1
swapy = 1
warpdest = [[-1500., 1500.],
[ 1500., 1500.],
[ 1500.,-1500.],
[-1500.,-1500.]]
[ 1500., 1500.],
[ 1500.,-1500.],
[-1500.,-1500.]]
[laser2]
pl = 2
@ -59,9 +60,9 @@ finangle = -0.008
swapx = 1
swapy = 1
warpdest = [[-1500., 1500.],
[ 1500., 1500.],
[ 1500.,-1500.],
[-1500.,-1500.]]
[ 1500., 1500.],
[ 1500.,-1500.],
[-1500.,-1500.]]
[laser3]
pl = 3
@ -78,7 +79,7 @@ finangle = 0.0
swapx = -1
swapy = -1
warpdest = [[-1500., 1500.],
[ 1500., 1500.],
[ 1500.,-1500.],
[-1500.,-1500.]]
[ 1500., 1500.],
[ 1500.,-1500.],
[-1500.,-1500.]]

View File

@ -29,6 +29,8 @@ print ""
import settings
settings.Read()
import cli
settings.Write()
from multiprocessing import Process, Queue, TimeoutError
import random, ast
@ -65,116 +67,6 @@ def dac_process(number, pl):
'''
def Laserver():
#for laserid in range(0,4):
# r.set('/lack/'+str(laserid),0)
# r.set('/lstt/'+str(laserid),0)
# Some random lists for all lasers at launch.
print ""
print "Creating startup point lists..."
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)]
if r.set('/pl/0', str(random_points)) == True:
print "/pl/0 ", ast.literal_eval(r.get('/pl/0'))
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)]
if r.set('/pl/1', str(random_points)) == True:
print "/pl/1 ", ast.literal_eval(r.get('/pl/1'))
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)]
if r.set('/pl/2', str(random_points)) == True:
print "/pl/2 ", ast.literal_eval(r.get('/pl/2'))
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)]
if r.set('/pl/3', str(random_points)) == True:
print "/pl/3 ", ast.literal_eval(r.get('/pl/3'))
# Order all lasers to show these random shapes at startup -> tell all 4 laser process to USER PLs
r.set('/order', "0")
# Launch one process (a newdacp instance) by etherdream
print ""
dac_worker0= Process(target=dac_process,args=(0,0))
print "Launching Laser 0 Process..."
dac_worker0.start()
if lasernumber >0:
dac_worker1= Process(target=dac_process,args=(1,0))
print "Launching Laser 1 Process..."
dac_worker1.start()
if lasernumber >1:
dac_worker2= Process(target=dac_process,args=(2,0))
print "Launching Laser 2 Process..."
dac_worker2.start()
if lasernumber >2:
dac_worker3= Process(target=dac_process,args=(3,0))
print "Launching Laser 3 Process..."
dac_worker3.start()
# Main loop do nothing. Maybe do the webui server ?
try:
#while True:
# Websocket startup
server = WebsocketServer(wsPORT,host=serverIP)
# Launch OSC thread listening to Bhorosc
print ""
print "Launching webUI OSC Handler..."
thread.start_new_thread(osc_thread, ())
# Default OSC handler for all incoming message from Bhorosc
oscserver.addMsgHandler("default", handler)
#print server
print ""
print "Launching webUI Websocket server..."
print "at :", serverIP, "port :",wsPORT
server.set_fn_new_client(new_client)
server.set_fn_client_left(client_left)
server.set_fn_message_received(message_received)
server.run_forever()
print ""
print "Running..."
except KeyboardInterrupt:
pass
# Gently stop on CTRL C
finally:
dac_worker0.join()
if lasernumber >0:
dac_worker1.join()
if lasernumber >1:
dac_worker2.join()
if lasernumber >2:
dac_worker3.join()
for laserid in range(0,lasernumber+1):
print "reset redis values for laser",laserid
r.set('/lack/'+str(laserid),64)
r.set('/lstt/'+str(laserid),64)
r.set('/cap/'+str(laserid),0)
print "Fin des haricots"
'''
#
# webUI server
#
@ -215,14 +107,6 @@ bhoroscPORTout = 8001
NozoscIPout = nozoscIP
NozoscPORTout = 8003
#
# OSC part
#
print ""
print "Launching Bhorosc commands receiver..."
print "at", bhoroscIPin, "port",str(bhoroscPORTin)
oscserver = OSCServer( (bhoroscIPin, bhoroscPORTin) )
oscserver.timeout = 0
OSCRunning = True
@ -283,12 +167,14 @@ def sendnozosc(oscaddress,oscargs=''):
def handler(path, tags, args, source):
oscpath = path.split("/")
pathlength = len(oscpath)
if debug >0:
print "debug", gstt.debug
if gstt.debug >0:
print ""
print "default handler"
print "Bhorosc said : ", path, oscpath, args
sendWSall(path + " " + str(args[0]))
las.handler(oscpath,args)
'''
# /lstt/number value
@ -313,8 +199,7 @@ def osc_frame():
# OSC Thread. Bhorosc handler and Automated status sender to UI.
def osc_thread():
print "Launching Automatic Dac status and bhorosc forwarder."
print "Will use Redis server IP ", serverIP
#print "Will use Redis server IP ", serverIP
'''
r = redis.StrictRedis(host=serverIP, port=6379, db=0)
@ -394,6 +279,20 @@ def new_client(client, server):
print("New WS client connected and was given id %d" % client['id'])
sendWSall("/status Hello %d" % 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]))
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, server):
print("WS Client(%d) disconnected" % client['id'])
@ -403,13 +302,15 @@ def client_left(client, server):
def message_received(client, server, message):
if len(message) > 200:
message = message[:200]+'..'
if debug >0:
if gstt.debug >0:
print("WS Client(%d) said: %s" % (client['id'], message))
oscpath = message.split(" ")
# current UI has no dedicated off button so /on 0 trigs /off to bhorosc
if oscpath[0] == "/on":
if oscpath[1] == "1":
sendbhorosc("/on")
else:
sendbhorosc("/off")
@ -426,7 +327,7 @@ def handle_timeout(self):
def sendWSall(message):
if debug >0:
if gstt.debug >0:
print("WS sending %s" % (message))
server.send_message_to_all(message)
@ -452,11 +353,14 @@ random_points = [(300.0+random.randint(-100, 100), 200.0+random.randint(-100, 10
if r.set('/pl/3', str(random_points)) == True:
print "/pl/3 ", ast.literal_eval(r.get('/pl/3'))
# Order all lasers to show these random shapes at startup -> tell all 4 laser process to USER PLs
r.set('/order', "0")
for laserid in range(0,lasernumber+1):
r.set('/order/'+str(laserid), 0)
print ""
print "Etherdream connection check is NOT DISPLAYED"
# Launch one process (a newdacp instance) by etherdream
print ""
@ -484,25 +388,29 @@ try:
#while True:
# Websocket startup
server = WebsocketServer(wsPORT,host=serverIP)
# Launch OSC thread listening to Bhorosc
print ""
print "Launching webUI OSC Handler..."
print "Launching OSC server..."
print "at", bhoroscIPin, "port",str(bhoroscPORTin)
print "Will update webUI dac status every second"
thread.start_new_thread(osc_thread, ())
# Default OSC handler for all incoming message from Bhorosc
oscserver.addMsgHandler("default", las.handler)
# Default OSC handler for all OSC incoming message
oscserver.addMsgHandler("default", handler)
#print server
print ""
print "Launching webUI Websocket server..."
print "at :", serverIP, "port :",wsPORT
print "at", serverIP, "port",wsPORT
server.set_fn_new_client(new_client)
server.set_fn_client_left(client_left)
server.set_fn_message_received(message_received)
server.run_forever()
print ""
print "Running..."
print "ws server running forver..."
server.run_forever()
except KeyboardInterrupt:
pass

View File

@ -251,15 +251,15 @@ class DAC(object):
# Lower case pl is the actual point list coordinates
self.pl = ast.literal_eval(r.get('/pl/'+str(self.mylaser)))
#if self.mylaser ==0:
print "DAC Init Laser", self.mylaser
#print "DAC Init Laser", self.mylaser
#print "pl :", self.pl
#print "EDH/"+str(self.mylaser),r.get('/EDH/'+str(self.mylaser))
if r.get('/EDH/'+str(self.mylaser)) == None:
print "Laser",self.mylaser,"NO EDH !! Computing one..."
#print "Laser",self.mylaser,"NO EDH !! Computing one..."
homographyp.newEDH(self.mylaser)
else:
gstt.EDH[self.mylaser] = np.array(ast.literal_eval(r.get('/EDH/'+str(self.mylaser))))
print "Laser",self.mylaser,"found its EDH in redis"
#print "Laser",self.mylaser,"found its EDH in redis"
#print gstt.EDH[self.mylaser]
'''
@ -273,13 +273,15 @@ class DAC(object):
self.xyrgb = self.xyrgb_prev = (0,0,0,0,0)
self.newstream = self.OnePoint()
if gstt.debug >0:
if self.connstatus != 0:
#print ""
print "Connection ERROR",self.connstatus,"with laser", str(mylaser),":",str(gstt.lasersIPS[mylaser])
#print "first 10 points in PL",self.PL, self.GetPoints(10)
else:
print "Connection status for", self.mylaser,":", self.connstatus
print "Connection status for", self.mylaser,":", self.connstatus
#print 'debug', debug
if self.connstatus != 0:
print ""
print "Connection ERROR",self.connstatus,"with laser", str(mylaser),":",str(gstt.lasersIPS[mylaser])
#print "first 10 points in PL",self.PL, self.GetPoints(10)
# Reference points
# Read the "hello" message
@ -348,10 +350,43 @@ class DAC(object):
while True:
#print "laser", self.mylaser, "Pb : ",self.last_status.playback_state
# update drawing parameters from redis keys
order = int(r.get('/order'))
order = r.get('/order/'+str(self.mylaser))
if order == 0:
# USER point list
self.pl = ast.literal_eval(r.get('/pl/'+str(self.mylaser)))
else:
# recompute EDH
if order == 1:
print "Laser",self.mylaser,"new EDH ORDER in redis"
gstt.EDH[self.mylaser]= np.array(ast.literal_eval(r.get('/EDH/'+str(self.mylaser))))
# Back to user point list
r.set('/order/'+str(self.mylaser), 0)
# BLACK point list
if order == 2:
print "Laser",self.mylaser,"BLACK ORDER in redis"
self.pl = black_points
# GRID point list
if order == 3:
print "Laser",self.mylaser,"GRID ORDER in redis"
self.pl = grid_points
# Resampler Modification
if order == 4:
self.resampler = ast.literal_eval(r.get('/resampler/'+str(self.mylaser)))
print "resampler for", self.mylaser, ":",self.resampler
gstt.stepshortline = self.resampler[0]
gstt.stepslongline[0] = self.resampler[1]
gstt.stepslongline[1] = self.resampler[2]
gstt.stepslongline[2] = self.resampler[3]
'''
#Laser order bit 0 = 0
if not order & (1 << (self.mylaser*2)):
#print "laser",mylaser,"bit 0 : 0"
@ -385,22 +420,6 @@ class DAC(object):
# Laser bit 0 = 1 and bit 1 = 1 : GRID PL
#print "laser",mylaser,"bit 1 : 1"
self.pl = grid_points
#self.pl = ast.literal_eval(r.get('/pl/'+str(self.mylaser)))
#if self.mylaser == 0:
# print "franken pl for ", self.mylaser, ":", self.pl
#print "franken 0 point :", self.pl[0]
'''
self.resampler = ast.literal_eval(r.get('/resampler/'+str(self.mylaser)))
print "resampler for", self.mylaser, ":",self.resampler
gstt.stepshortline = self.resampler[0]
gstt.stepslongline[0] = self.resampler[1]
gstt.stepslongline[1] = self.resampler[2]
gstt.stepslongline[2] = self.resampler[3]
'''
r.set('/lstt/'+str(self.mylaser), self.last_status.playback_state)

View File

@ -24,6 +24,7 @@ def Write():
config.set('General', 'ljayserverip', str(gstt.LjayServerIP))
config.set('General', 'bhoroscip', str(gstt.oscIPin))
config.set('General', 'nozoscip', str(gstt.nozoscIP))
config.set('General', 'debug', str(gstt.debug))
for i in range(gstt.LaserNumber):
laser = 'laser' + str(i)
@ -52,6 +53,7 @@ def Read():
gstt.LjayServerIP= config.get('General', 'ljayserverip')
gstt.oscIPin = config.get('General', 'bhoroscip')
gstt.nozoscip = config.get('General', 'nozoscip')
gstt.debug = config.get('General', 'debug')
for i in range(4):
laser = 'laser' + str(i)