343 lines
8.0 KiB
Python
343 lines
8.0 KiB
Python
#!/usr/bin/env python
|
|
|
|
# WS server example that synchronizes state across clients
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
import websockets
|
|
import redis
|
|
import time
|
|
import serial
|
|
import numpy as np
|
|
import RPi.GPIO as GPIO
|
|
import threading
|
|
|
|
import os
|
|
|
|
def is_raspberrypi():
|
|
try:
|
|
with io.open('/sys/firmware/devicetree/base/model', 'r') as m:
|
|
if 'raspberry pi' in m.read().lower(): return True
|
|
except Exception: pass
|
|
return False
|
|
print(is_raspberrypi)
|
|
|
|
|
|
# Colordefinition for console print
|
|
class pcol:
|
|
HEADER = '\033[95m'
|
|
OKBLUE = '\033[94m'
|
|
OKGREEN = '\033[92m'
|
|
WARNING = '\033[93m'
|
|
FAIL = '\033[91m'
|
|
ENDC = '\033[0m'
|
|
BOLD = '\033[1m'
|
|
UNDERLINE = '\033[4m'
|
|
|
|
|
|
|
|
#################################################################################
|
|
#
|
|
#
|
|
# G L O B A L S
|
|
#
|
|
|
|
# DMX array config
|
|
DMXDATA = np.zeros([513], dtype='uint8')
|
|
|
|
# DMX Startcode
|
|
DMXDATA[0] = 0 # 0 = Lighting Control Data
|
|
|
|
# DMX Reset [µs]/float
|
|
DMXRST = 88.0 # typical 88µs
|
|
|
|
# DMX Break [µs]/float
|
|
DMXBRK = 8.0 # 8µs (min) to 1s (max)
|
|
|
|
# DMX Sequence sleep [ms]/float
|
|
DMXSLP = 250.0 # 0µs (min) to 1s (max)
|
|
|
|
# Buffer for RDM
|
|
DMXRDM = "<initial>"
|
|
DMXRDMFLAG = True # 1 for radlines() is active to buffer rdm packages
|
|
|
|
|
|
|
|
|
|
#
|
|
# Websocket variables
|
|
#
|
|
|
|
# Set to store user-data
|
|
USERS = set()
|
|
|
|
# Set to store other data served by the websocket
|
|
# STATE = {}
|
|
# DummyFill
|
|
# for i in range(1, 10):
|
|
# STATE.update({str(i): 0})
|
|
|
|
|
|
|
|
|
|
|
|
#################################################################################
|
|
#
|
|
#
|
|
# F U N C T I O N S
|
|
#
|
|
#
|
|
###########################
|
|
#
|
|
# DMX
|
|
#
|
|
###########################
|
|
|
|
|
|
def sendDMX(data):
|
|
#continuously sending the signal; to receive changed values: Threading
|
|
global DMXRDMFLAG
|
|
global DMXRDM
|
|
global DMXSLP
|
|
|
|
while True:
|
|
with serial.Serial('/dev/serial0', baudrate=250000, timeout=(DMXSLP/1000.0), stopbits=serial.STOPBITS_TWO, bytesize=8) as DMXSER:# UART configuration and definition
|
|
#DMXSER.cancel_read()
|
|
|
|
# GPIO_18 to switch RS-485 driver (IC1) to TX-mode
|
|
GPIO.output(18, GPIO.HIGH)
|
|
|
|
# send reset
|
|
DMXSER.send_break(DMXRST / 1000000.0)
|
|
|
|
# break-time
|
|
time.sleep(DMXBRK / 1000000.0)
|
|
|
|
# send complete data array
|
|
DMXSER.write(bytearray(DMXDATA))
|
|
|
|
|
|
|
|
|
|
# GPIO_18 to switch RS-485 driver (IC1) to RX-mode
|
|
DMXSER.reset_input_buffer()
|
|
GPIO.output(18, GPIO.LOW)
|
|
|
|
# Sleep between TX packages, use time to get RDM packages
|
|
print("start read for ", DMXSLP, "ms")
|
|
DMXRDM = DMXSER.read(400)
|
|
print("end read, input is ", DMXRDM)
|
|
|
|
|
|
|
|
|
|
|
|
###########################
|
|
#
|
|
# THREADING
|
|
#
|
|
###########################
|
|
slicy = threading.Thread(target=sendDMX, args=(DMXDATA, ))
|
|
slicy.start()
|
|
|
|
|
|
|
|
|
|
|
|
###########################
|
|
#
|
|
# Websockset
|
|
#
|
|
###########################
|
|
|
|
async def rdm_state():
|
|
global DMXRDM
|
|
global DMXRDMFLAG
|
|
if USERS and DMXRDMFLAG:
|
|
#message = "rdm=" + DMXRDM + ";"
|
|
print("RDM EVENT ", DMXRDM)
|
|
await asyncio.wait([user.send("rdm=test;") for user in USERS])
|
|
|
|
|
|
|
|
# Notify_State is triggered each time a new connection is established to the websocket
|
|
# and only sent to the new user, not to all!
|
|
# Serving all infos once as complete string, so we only have to send delta from that time on
|
|
async def notify_state():
|
|
if USERS: # asyncio.wait doesnt accept an empty list
|
|
message = state_event()
|
|
await asyncio.wait([user.send(message) for user in USERS])
|
|
|
|
# Generate message-text for Notify_State
|
|
def state_event():
|
|
|
|
# we have to send a string
|
|
rtn = ""
|
|
|
|
# dont have to send DMXDATA[0], this is the start code
|
|
for index in range(1, len(DMXDATA)):
|
|
rtn += (str(index) + "=" + str(DMXDATA[index]) + ";")
|
|
|
|
# give debug feedback
|
|
print("state_event returned ", rtn)
|
|
return rtn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Users-Event is triggered each time a new connection is established to the websocket
|
|
# and sent to all other but not the new user
|
|
async def notify_users():
|
|
if USERS: # asyncio.wait doesnt accept an empty list
|
|
message = users_event()
|
|
await asyncio.wait([user.send(message) for user in USERS])
|
|
|
|
# Generate message-text for Notify_Users
|
|
def users_event():
|
|
|
|
# only send the new value
|
|
return "users=" + str(len(USERS)) + ";"
|
|
|
|
|
|
# Handling (un-)registering
|
|
async def register(websocket):
|
|
USERS.add(websocket)
|
|
print("New user registered ")
|
|
await notify_users()
|
|
|
|
async def unregister(websocket):
|
|
USERS.remove(websocket)
|
|
print("Known user left")
|
|
await notify_users()
|
|
|
|
|
|
|
|
|
|
#
|
|
# Sending delta-reports to all users when a value changed except user count
|
|
#
|
|
async def notify_delta(delta):
|
|
if USERS: # asyncio.wait doesnt accept an empty list
|
|
await asyncio.wait([user.send(delta) for user in USERS])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
# Websocket working thread
|
|
#
|
|
async def websworker(websocket, path):
|
|
|
|
# register(websocket) sends user_event() to websocket
|
|
await register(websocket)
|
|
try:
|
|
await websocket.send(state_event())
|
|
while True:
|
|
# waiting for a new message resceived on the websocket
|
|
message = await websocket.recv()
|
|
|
|
# split message from multiple commands in one string
|
|
data = message.split(";")
|
|
|
|
# process each command
|
|
for index in range(len(data)):
|
|
# act[0] will be the key
|
|
# act[1] will be the value
|
|
act = data[index].split("=")
|
|
|
|
try:
|
|
# if key is a number, we will have e change on a dmx value
|
|
# otherwise, we have a string to check which will be processed in except...
|
|
# poor python - there is no other chance to check if a string can be a int without errors
|
|
dmxchannel = int(act[0])
|
|
dmxvalue = int(act[1])
|
|
|
|
# We have integer, but are they in a valid range?
|
|
if( dmxchannel < 1 or dmxchannel > 512 or dmxvalue < 0 or dmxvalue > 255):
|
|
# this must be an exception, we will continue in the exception-area anyway now
|
|
dmxchannel = int("will raise")
|
|
|
|
# looks valid, so take them to the buffer
|
|
DMXDATA[dmxchannel] = dmxvalue
|
|
|
|
# notifying our connected users
|
|
# we can send the resceived command back since the syntax is matched :)
|
|
await notify_delta(data[index])
|
|
|
|
# debug
|
|
print("DMX value for channel " + act[0] + " changed to " + act[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# It does not look like we have a dmx key-value, so we have to process the command
|
|
except Exception:
|
|
|
|
if act[0] == "manual":
|
|
sendDMX(DMXDATA)
|
|
print("Manual send data: ", bytearray(DMXDATA))
|
|
|
|
elif act[0] == "sec":
|
|
print("")
|
|
|
|
else:
|
|
print("received message, but can not handle: ", act)
|
|
|
|
finally:
|
|
await unregister(websocket)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
###########################
|
|
#
|
|
# GPIO
|
|
#
|
|
###########################
|
|
# We have to set GPIO
|
|
GPIO.setmode(GPIO.BCM)
|
|
GPIO.setwarnings(False)
|
|
|
|
GPIO.setup(18, GPIO.OUT)
|
|
GPIO.setup(4, GPIO.OUT)
|
|
|
|
# GPIO_18 to switch RS-485 driver (IC1) to RX-mode
|
|
#GPIO.output(18, GPIO.LOW)
|
|
# V2: will be done in send-loop for rdm
|
|
|
|
# Powercycle 3V3 on HAT to ensure a cleaned up buffer
|
|
GPIO.output(4, GPIO.HIGH)
|
|
time.sleep(0.25)
|
|
GPIO.output(4, GPIO.LOW)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
###########################
|
|
#
|
|
# Start the websocket
|
|
#
|
|
###########################
|
|
|
|
asyncio.get_event_loop().run_until_complete(
|
|
websockets.serve(websworker, port=6789))
|
|
asyncio.get_event_loop().run_forever()
|