LJ/libs3/tracer_etherdream.py

401 lines
15 KiB
Python
Raw Normal View History

2023-02-24 12:37:49 +00:00
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
'''
Tracer v0.8.2
Etherdream DACs handler on network via Redis
LICENCE : CC
Sam Neurohack, pclf
Includes live conversion in etherdream coordinates, geometric corrections, color balance change, intensity limitation, grid display,...
One tracer process is launched per requested laser by LJ. Lasers parameters in LJ.conf.
Live I/O based on redis keys : inputs (Pointlists to draw,...) and outputs (DAC state, errors,..).
Keys are mostly read and set at each main loop.
This tracer include an enhanced version (support for several lasers) of the etherdream python library from j4cDAC.
* Redis keys reference *
- Drawing things :
/pl/Scene/lasernumber [(x,y,color),(x1,y1,color),...] The live list of drawn pygame points. Tracer continously ask redis for key /clientkey+lasernumber
/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 "/pl/SceneNumber/" What Scene to retrieve from redis
/EDH/lasernumber
- Tracer control :
/order 0-8 Set redis key with new value then issue the order number
0 : Draw Normal point list
1 : Get the new EDH = reread redis key /EDH/lasernumber
2 : Draw BLACK point list
3 : Draw GRID point list
4 : Resampler Change (longs and shorts lsteps)
5 : Client Key Change = reread redis key /clientkey
6 : Max Intensity Change = reread redis key /intensity
7 : kpps change = reread redis key /kpps
8 : color balance change = reread redis keys /red /green /blue
- Managing Etherdream DACs :
Discrete drawing values
/kpps 0- DAC output speed to laser, then order 7. Depends of actual angle
/intensity 0-255 Laser output power, then order 6 (for alignement,...)
/red 0-100 % of full red, then order 8
/green 0-100 % of full green, then order 8
/blue 0-100 % of full blue, then order 8
DAC status report
/lstt/lasernumber etherdream last_status.playback_state (0: idle 1: prepare 2: playing)
/cap/lasernumber number of empty points sent to fill etherdream buffer (up to 1799)
/lack/lasernumber "a": ACK "F": Full "I": invalid. 64 or 35 for no connection.
Geometric corrections
Doctodo
'''
import socket
import time
import struct
# from gstt import debug
from libs3 import gstt, log
import math
import ast
from libs3 import homographyp
import numpy as np
from .tracer_common import *
# @todo this needs normallization
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 ?"}
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)]
class TracerEtherdream(Tracer):
"""A connection to a DAC."""
# "Laser point List" Point generator
# each points is yielded : Getpoints() call n times OnePoint()
def __init__(self, mylaser, PL, redis, port=7765):
"""Connect to the DAC over TCP."""
socket.setdefaulttimeout(2)
self.redis = redis
self.mylaser = mylaser
self.clientkey = self.redis.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
self.redis.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(self.redis.get(self.clientkey + str(self.mylaser)).decode('ascii'))
if self.redis.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(self.redis.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.intred = 100
self.intgreen = 100
self.intblue = 100
self.newstream = self.OnePoint()
self.prev_x = 0
self.prev_y = 0
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 before_loop(self):
# print("laser", self.laser_id, "Pb : ",self.last_status.playback_state)
# error if DAC is already playing state (from other source)
if self.last_status.playback_state == 2:
raise Exception("already playing?!")
# if idle go to prepare state
elif self.last_status.playback_state == 0:
self.prepare()
def get_points_capacity(self):
cap = 1799 - self.last_status.fullness
if cap < 100:
time.sleep(0.001)
cap += 150
return
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:])
self.redis.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 begin(self, lwm, rate):
cmd = struct.pack("<cHI", b'b', lwm, rate)
print("Tracer", str(self.mylaser), "begin with PL : ", str(self.PL))
self.conn.sendall(cmd)
return self.readresp("b")
def update(self, lwm, rate):
print(("update", lwm, rate))
cmd = struct.pack("<cHI", b'u', lwm, rate)
self.conn.sendall(cmd)
return self.readresp("u")
def encode_point(self, point):
return pack_point(self.mylaser, self.intensity, *point)
def get_capacity(self):
""" How much points can we send next / are free in etherdream's buffer?"""
cap = 1799 - self.last_status.fullness
return cap
def write(self, points):
epoints = list(map(self.encode_point, points))
cmd = struct.pack("<cH", b'd', len(epoints))
self.conn.sendall(cmd + b''.join(epoints))
return self.readresp('d')
def get_warped_point(self, x, y ):
# Etherpoint all transform in one matrix, with warp !!
# xy : x y
# gstt.EDH[self.mylaser]= np.array(ast.literal_eval(self.redis.get('/EDH/'+str(self.mylaser))))
position = homographyp.apply(gstt.EDH[self.laser_id], np.array([(x, y)]))
# 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]
def prepare(self):
self.conn.sendall(b'p')
return self.readresp('p')
def stop(self):
self.conn.sendall('s')
return self.readresp('s')
def estop(self):
self.conn.sendall("\xFF")
return self.readresp("\xFF")
def clear_estop(self):
self.conn.sendall("c")
return self.readresp("c")
def ping(self):
self.conn.sendall('?')
return self.readresp('?')
def play_stream(self):
# print("laser", self.mylaser, "Pb : ",self.last_status.playback_state)
# error if etherdream is already playing state (from other source)
if self.last_status.playback_state == 2:
raise Exception("already playing?!")
# if idle go to prepare state
elif self.last_status.playback_state == 0:
self.prepare()
started = 0
while True:
# print("laser", self.mylaser, "Pb : ",self.last_status.playback_state)
order = int(self.redis.get('/order/' + str(self.mylaser)).decode('ascii'))
# print("tracer", str(self.mylaser),"order", order, type(order)
if order == 0:
# USER point list
# self.pl = ast.literal_eval(self.redis.get(self.clientkey+str(self.mylaser)).decode('ascii'))
# print("Tracer : laser", self.mylaser, " order 0 : pl : ",len(self.pl))
# si la clef est vide utiliser les points noirs ? -> syntax error -> black points.
try:
# newpl = ""
# newpl = self.redis.get(self.clientkey+str(self.mylaser))
# self.pl = ast.literal_eval(newpl.decode('ascii'))
self.pl = ast.literal_eval(self.redis.get(self.clientkey + str(self.mylaser)).decode('ascii'))
except SyntaxError:
print("BAD POINTLIST on Tracer : laser", self.mylaser, " order 0 : pl : ", self.pl)
self.pl = black_points
# print("Tracer : laser", self.mylaser, " order 0 : pl : ",len(self.pl))
else:
# Get the new EDH
if order == 1:
print("Tracer", self.mylaser, "new EDH ORDER in redis")
gstt.EDH[self.mylaser] = np.array(
ast.literal_eval(self.redis.get('/EDH/' + str(self.mylaser)).decode('ascii')))
# Back to user point list
self.redis.set('/order/' + str(self.mylaser), 0)
# BLACK point list
if order == 2:
print("Tracer", self.mylaser, "BLACK ORDER in redis")
self.pl = black_points
# GRID point list
if order == 3:
print("Tracer", self.mylaser, "GRID ORDER in redis")
self.pl = grid_points
# Resampler Change
if order == 4:
self.resampler = ast.literal_eval(self.redis.get('/resampler/' + str(self.mylaser)).decode('ascii'))
print("Tracer", self.mylaser, " : resetting lsteps for", 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]
# Back to user point list order
self.redis.set('/order/' + str(self.mylaser), 0)
# Client Key change
if order == 5:
print("Tracer", self.mylaser, "new clientkey")
self.clientkey = self.redis.get('/clientkey')
# Back to user point list order
self.redis.set('/order/' + str(self.mylaser), 0)
# Intensity change
if order == 6:
self.intensity = int(self.redis.get('/intensity/' + str(self.mylaser)).decode('ascii')) << 8
print("Tracer", self.mylaser, "new Intensity", self.intensity)
gstt.intensity[self.mylaser] = self.intensity
self.redis.set('/order/' + str(self.mylaser), "0")
# kpps change
if order == 7:
gstt.kpps[self.mylaser] = int(self.redis.get('/kpps/' + str(self.mylaser)).decode('ascii'))
print("Tracer", self.mylaser, "new kpps", gstt.kpps[self.mylaser])
self.update(0, gstt.kpps[self.mylaser])
self.redis.set('/order/' + str(self.mylaser), "0")
# color balance change
if order == 8:
self.intred = int(self.redis.get('/red/' + str(self.mylaser)).decode('ascii'))
self.intgreen = int(self.redis.get('/green/' + str(self.mylaser)).decode('ascii'))
self.intblue = int(self.redis.get('/blue/' + str(self.mylaser)).decode('ascii'))
print("Tracer", self.mylaser, "new color balance", self.intred, "% ", self.intgreen, "% ",
self.intblue, "% ")
self.redis.set('/order/' + str(self.mylaser), "0")
self.redis.set('/lstt/' + str(self.mylaser), self.last_status.playback_state)
# pdb.set_trace()
# How much room?
cap = 1799 - self.last_status.fullness
points = self.GetPoints(cap)
self.redis.set('/cap/' + str(self.mylaser), cap)
if cap < 100:
time.sleep(0.001)
cap += 150
# print("Writing %d points" % (cap, ))
# t0 = time.time()
# if self.mylaser == 2:
# print(points)
self.write(points)
# t1 = time.time()
# print("Took %f" % (t1 - t0, )
if not started:
print("Tracer", self.mylaser, "starting with", gstt.kpps[self.mylaser], "kpps")
self.begin(0, gstt.kpps[self.mylaser])
started = 1