#!/usr/bin/python3 # -*- coding: utf-8 -*- # -*- mode: Python -*- ''' Tracer for LJ v0.8.2 Enhanced version (support for several lasers) of the etherdream python library from j4cDAC. One tracer process is launched per requested laser. I/O based on redis keys. LICENCE : CC Sam Neurohack, pclf Uses redis keys value for live inputs (Pointlists to draw,...) and outputs (DAC state, errors,..). Most of redis keys are read and set at each main loop. Includes live conversion in etherdream coordinates, geometric corrections,... Etherdream IP is found in conf file for given laser number. (LJ.conf) Redis keys to draw things : /order select some change to adjust. See below /pl/lasernumber [(x,y,color),(x1,y1,color),...] A string of list of points list. /resampler/lasernumber [(1.0,8), (0.25,3),(0.75,3),(1.0,10)] : a string for resampling rules. the first tuple (1.0,8) is for short line < 4000 in etherdream space (0.25,3),(0.75,3),(1.0,10) for long line > 4000 i.e (0.25,3) means go at 25% position on the line, send 3 times this position to etherdream /clientkey Redis keys for Etherdream DAC - Control /kpps see order 7 /intensity see order 6 /red see order 8 /green see order 8 /blue see order 8 - DAC status report /lstt/lasernumber value etherdream last_status.playback_state (0: idle 1: prepare 2: playing) /cap/lasernumber value number of empty points sent to fill etherdream buffer (up to 1799) /lack/lasernumber value "a": ACK "F": Full "I": invalid. 64 or 35 for no connection. Order 0 : Draw Normal point list 1 : Get the new EDH 2 : Draw BLACK point list 3 : Draw GRID point list 4 : Resampler Change (longs and shorts lsteps) 5 : Client Key change 6 : Intensity change 7 : kpps change 8 : color balance change Geometric corrections : Doctodo ''' import socket import time import struct #from gstt import debug from libs3 import gstt,log import math from itertools import cycle #from globalVars import * import pdb import ast import redis from libs3 import homographyp import numpy as np import binascii black_points = [(278.0,225.0,0),(562.0,279.0,0),(401.0,375.0,0),(296.0,454.0,0),(298.0,165.0,0)] grid_points = [(300.0,200.0,0),(500.0,200.0,65280),(500.0,400.0,65280),(300.0,400.0,65280),(300.0,200.0,65280),(300.0,200.0,0),(200.0,100.0,0),(600.0,100.0,65280),(600.0,500.0,65280),(200.0,500.0,65280),(200.0,100.0,65280)] r = redis.StrictRedis(host=gstt.LjayServerIP, port=6379, db=0) # r = redis.StrictRedis(host=gstt.LjayServerIP , port=6379, db=0, password='-+F816Y+-') ackstate = {'61': 'ACK', '46': 'FULL', '49': "INVALID", '21': 'STOP', '64': "NO CONNECTION ?", '35': "NO CONNECTION ?" , '97': 'ACK', '70': 'FULL', '73': "INVALID", '33': 'STOP', '100': "NOCONNECTION", '48': "NOCONNECTION", 'a': 'ACK', 'F': 'FULL', 'I': "INVALID", '!': 'STOP', 'd': "NO CONNECTION ?", '0': "NO CONNECTION ?"} lstate = {'0': 'IDLE', '1': 'PREPARE', '2': "PLAYING", '64': "NOCONNECTION ?" } def pack_point(laser,intensity, x, y, r, g, b, i = -1, u1 = 0, u2 = 0, flags = 0): """Pack some color values into a struct dac_point.""" #print("Tracer", laser,":", r,g,b,"intensity", intensity, "i", i) if r > intensity: r = intensity if g > intensity: g = intensity if b > intensity: b = intensity if max(r, g, b) == 0: i = 0 else: i = intensity #print("Tracer", laser,":", r,g,b,"intensity", intensity, "i", i) #print(x, type(x), int(x)) return struct.pack(" 1: self.status.dump(prefix) class DAC(object): """A connection to a DAC.""" # "Laser point List" Point generator # each points is yielded : Getpoints() call n times OnePoint() def OnePoint(self): while True: #pdb.set_trace() for indexpoint,currentpoint in enumerate(self.pl): #print indexpoint, currentpoint xyc = [currentpoint[0],currentpoint[1],currentpoint[2]] self.xyrgb = self.EtherPoint(xyc) delta_x, delta_y = self.xyrgb[0] - self.xyrgb_prev[0], self.xyrgb[1] - self.xyrgb_prev[1] #test adaptation selon longueur ligne if math.hypot(delta_x, delta_y) < 4000: # For glitch art : decrease lsteps #l_steps = [ (1.0, 8)] l_steps = gstt.stepshortline else: # For glitch art : decrease lsteps #l_steps = [ (0.25, 3), (0.75, 3), (1.0, 10)] l_steps = gstt.stepslongline for e in l_steps: step = e[0] for i in range(0,e[1]): self.xyrgb_step = (self.xyrgb_prev[0] + step*delta_x, self.xyrgb_prev[1] + step*delta_y) + self.xyrgb[2:] yield self.xyrgb_step self.xyrgb_prev = self.xyrgb def GetPoints(self, n): d = [next(self.newstream) for i in range(n)] #print d return d # Etherpoint all transform in one matrix, with warp !! # xyc : x y color def EtherPoint(self,xyc): c = xyc[2] #print("") #print("pygame point",[(xyc[0],xyc[1],xyc[2])]) #gstt.EDH[self.mylaser]= np.array(ast.literal_eval(r.get('/EDH/'+str(self.mylaser)))) position = homographyp.apply(gstt.EDH[self.mylaser],np.array([(xyc[0],xyc[1])])) #print("etherdream point",position[0][0], position[0][1], ((c >> 16) & 0xFF) << 8, ((c >> 8) & 0xFF) << 8, (c & 0xFF) << 8) return (position[0][0], position[0][1], ((c >> 16) & 0xFF) << 8, ((c >> 8) & 0xFF) << 8, (c & 0xFF) << 8) def read(self, l): """Read exactly length bytes from the connection.""" while l > len(self.buf): self.buf += self.conn.recv(4096) obuf = self.buf self.buf = obuf[l:] return obuf[:l] def readresp(self, cmd): """Read a response from the DAC.""" data = self.read(22) response = data[0] gstt.lstt_dacanswers[self.mylaser] = response cmdR = chr(data[1]) status = Status(data[2:]) r.set('/lack/'+str(self.mylaser), response) if cmdR != cmd: raise ProtocolError("expected resp for %r, got %r" % (cmd, cmdR)) if response != ord('a'): raise ProtocolError("expected ACK, got %r" % (response, )) self.last_status = status return status def __init__(self, mylaser, PL, port = 7765): """Connect to the DAC over TCP.""" socket.setdefaulttimeout(2) self.mylaser = mylaser self.clientkey = r.get("/clientkey").decode('ascii') #log.info("Tracer "+str(self.mylaser)+" connecting to "+ gstt.lasersIPS[mylaser]) #print("DAC", self.mylaser, "Handler process, connecting to", gstt.lasersIPS[mylaser] ) self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connstatus = self.conn.connect_ex((gstt.lasersIPS[mylaser], port)) if self.connstatus == 35 or self.connstatus == 64: log.err("Tracer "+ str(self.mylaser)+" ("+ gstt.lasersIPS[mylaser]+"): "+ackstate[str(self.connstatus)]) else: print("Tracer "+ str(self.mylaser)+" ("+ gstt.lasersIPS[mylaser]+"): "+ackstate[str(self.connstatus)]) # ipconn state is -1 at startup (see gstt) and modified here r.set('/lack/'+str(self.mylaser), self.connstatus) gstt.lstt_ipconn[self.mylaser] = self.connstatus self.buf = b'' # Upper case PL is the Point List number self.PL = PL # Lower case pl is the actual point list coordinates #pdb.set_trace() self.pl = ast.literal_eval(r.get(self.clientkey + str(self.mylaser)).decode('ascii')) if r.get('/EDH/'+str(self.mylaser)) == None: #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)).decode('ascii'))) #print("Laser",self.mylaser,"found its EDH in redis") #print gstt.EDH[self.mylaser] self.xyrgb = self.xyrgb_prev = (0,0,0,0,0) self.intensity = 65280 self.newstream = self.OnePoint() if gstt.debug >0: print("Tracer",self.mylaser,"init asked for ckey", self.clientkey+str(self.mylaser)) if self.connstatus != 0: #print("" log.err("Connection ERROR " +str(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 DAC "+str(self.mylaser)+" : "+ str(self.connstatus)) # Reference points # Read the "hello" message first_status = self.readresp("?") first_status.dump() position = [] def begin(self, lwm, rate): cmd = struct.pack("