diff --git a/README.md b/README.md index c17b485..9e5cd81 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cli.py b/cli.py index 744e25c..0ad40fe 100644 --- a/cli.py +++ b/cli.py @@ -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() \ No newline at end of file diff --git a/gstt.py b/gstt.py index 561ff49..aa1f735 100644 --- a/gstt.py +++ b/gstt.py @@ -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= [[],[],[],[]] diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..c33abcc --- /dev/null +++ b/install.sh @@ -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 + + + diff --git a/las.py b/las.py index f2a4e6f..c66b6c0 100644 --- a/las.py +++ b/las.py @@ -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<0: - print "" - print "default handler" - print "Bhorosc said for laser",laser,": ", path, oscpath, args - - # /grid/lasernumber value (0 or 1) if oscpath[1] == "grid": diff --git a/mainy.conf b/mainy.conf index e0a7cdf..db89a4c 100644 --- a/mainy.conf +++ b/mainy.conf @@ -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.]] diff --git a/mainyservers.py b/mainyservers.py index 57b2223..d525069 100644 --- a/mainyservers.py +++ b/mainyservers.py @@ -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 diff --git a/newdacp.py b/newdacp.py index 676ccbc..1e4620a 100644 --- a/newdacp.py +++ b/newdacp.py @@ -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) diff --git a/settings.py b/settings.py index d10806e..6c7ad04 100644 --- a/settings.py +++ b/settings.py @@ -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)