Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/huecircle.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ def circle_generator():
circle = circle_generator()


def callback():
def callback(*args, **kwargs):
return next(circle)


if __name__ == '__main__':
p = Pyghthouse(UNAME, TOKEN, image_callback=callback)
p = Pyghthouse(UNAME, TOKEN, image_callback=callback, ignore_ssl_cert=True)
print("Starting... use CTRL+C to stop.")
p.start()
37 changes: 37 additions & 0 deletions examples/movingdot_remote_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from pyghthouse import Pyghthouse, VerbosityLevel, KeyEvent
from config import UNAME, TOKEN


def clip(val, min_val, max_val):
if val < min_val:
return min_val
if val > max_val:
return max_val
return val


def main_loop():
x = 0
y = 0
p = Pyghthouse(UNAME, TOKEN, verbosity=VerbosityLevel.NONE, stream_remote_inputs=True)
p.start()
while True:
img = p.empty_image()
img[y][x] = [255, 255, 255]
p.set_image(img)
for e in p.get_all_events():
if isinstance(e, KeyEvent) and e.down:
if e.code == 65: # A
x -= 1
elif e.code == 68: # D
x += 1
elif e.code == 87: # W
y -= 1
elif e.code == 83: # S
y += 1
x = clip(x, 0, 27)
y = clip(y, 0, 13)


if __name__ == '__main__':
main_loop()
7 changes: 6 additions & 1 deletion examples/noisefill.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ def image_gen():

g = image_gen()


def callback(*args, **kwargs):
return next(g)


if __name__ == '__main__':
p = Pyghthouse(UNAME, TOKEN, image_callback=g.__next__, frame_rate=60)
p = Pyghthouse(UNAME, TOKEN, image_callback=callback, frame_rate=60)
print("Starting... use CTRL+C to stop.")
p.start()
2 changes: 1 addition & 1 deletion examples/rainbow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def rainbow_generator():
rainbow = rainbow_generator()


def callback():
def callback(*args, **kwargs):
return next(rainbow)


Expand Down
7 changes: 6 additions & 1 deletion examples/rgbfill.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ def image_gen():

g = image_gen()


def callback(*args, **kwargs):
return next(g)


if __name__ == '__main__':
p = Pyghthouse(UNAME, TOKEN, image_callback=g.__next__, frame_rate=60)
p = Pyghthouse(UNAME, TOKEN, image_callback=callback, frame_rate=60)
print("Starting... use CTRL+C to stop.")
p.start()
7 changes: 6 additions & 1 deletion examples/rgbscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ def image_gen():

g = image_gen()


def callback(*args, **kwargs):
return next(g)


if __name__ == '__main__':
p = Pyghthouse(UNAME, TOKEN, image_callback=g.__next__, frame_rate=60)
p = Pyghthouse(UNAME, TOKEN, image_callback=callback, frame_rate=60)
print("Starting... use CTRL+C to stop.")
p.start()
2 changes: 1 addition & 1 deletion examples/twopoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def __init__(self):
self.draw = ImageDraw.Draw(self.img)
self.hue = 0.0

def callback(self):
def callback(self, events):
self.p1.update()
self.p2.update()
self.hue += COLOR_SPEED
Expand Down
94 changes: 94 additions & 0 deletions examples/twopoints_remote_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from random import random

import numpy as np
from PIL import Image, ImageDraw

from pyghthouse.utils import from_hsv
from pyghthouse import Pyghthouse, KeyEvent

from config import UNAME, TOKEN

SPEED_FACTOR = 0.02
COLOR_SPEED = 0.01


class BouncyPoint:

def __init__(self):
self.x = random()
self.y = random()
self.vx = SPEED_FACTOR * (random() + 0.5)
self.vy = SPEED_FACTOR * (random() + 0.5)

def update(self):
self.x += self.vx
self.y += self.vy

if self.x < 0:
self.vx *= -1
self.x *= -1
elif self.x > 1:
self.vx *= -1
self.x = 2 - self.x
if self.y < 0:
self.vy *= -1
self.y *= -1
elif self.y > 1:
self.vy *= -1
self.y = 2 - self.y


class ImageMaker:

def __init__(self):
self.p1 = BouncyPoint()
self.p2 = BouncyPoint()
self.img = Image.new('RGB', (280, 140))
self.draw = ImageDraw.Draw(self.img)
self.hue = 0.0
self.pressed_keys = {'l': False, 'u': False, 'r': False, 'd': False,
'L': False, 'U': False, 'R': False, 'D': False}

def callback(self, events):
for e in filter(lambda x: x is not None and isinstance(x, KeyEvent), events):
if e.code == 37:
self.pressed_keys['l'] = e.down
if e.code == 38:
self.pressed_keys['u'] = e.down
if e.code == 39:
self.pressed_keys['r'] = e.down
if e.code == 40:
self.pressed_keys['d'] = e.down
if e.code == 65:
self.pressed_keys['L'] = e.down
if e.code == 87:
self.pressed_keys['U'] = e.down
if e.code == 68:
self.pressed_keys['R'] = e.down
if e.code == 83:
self.pressed_keys['D'] = e.down

self.p1.vx = (self.pressed_keys['r'] - self.pressed_keys['l']) * SPEED_FACTOR
self.p1.vy = (self.pressed_keys['d'] - self.pressed_keys['u']) * SPEED_FACTOR
self.p2.vx = (self.pressed_keys['R'] - self.pressed_keys['L']) * SPEED_FACTOR
self.p2.vy = (self.pressed_keys['D'] - self.pressed_keys['U']) * SPEED_FACTOR
self.p1.update()
self.p2.update()
self.hue += COLOR_SPEED
self.draw.line([(self.p1.x * 280, self.p1.y * 140), (self.p2.x * 280, self.p2.y * 140)], width=10,
fill=tuple(from_hsv(self.hue, 1, 1)))

output = self.img.copy()
draw2 = ImageDraw.Draw(output)
draw2.rectangle((self.p1.x * 280 - 6, self.p1.y * 140 - 6, self.p1.x * 280 + 6, self.p1.y * 140 + 6),
fill=(255, 255, 255), outline=(255, 255, 255))
draw2.rectangle((self.p2.x * 280 - 6, self.p2.y * 140 - 6, self.p2.x * 280 + 6, self.p2.y * 140 + 6),
fill=(0, 0, 0), outline=(0, 0, 0))
output.thumbnail((28, 14))
return np.asarray(output)


if __name__ == '__main__':
i = ImageMaker()
p = Pyghthouse(UNAME, TOKEN, image_callback=i.callback, frame_rate=60, stream_remote_inputs=True)
p.start()
1 change: 1 addition & 0 deletions pyghthouse/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from pyghthouse.ph import Pyghthouse, VerbosityLevel
from pyghthouse.event.events import KeyEvent
15 changes: 13 additions & 2 deletions pyghthouse/connection/wsconnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __next__(self):
def __iter__(self):
return self

def __init__(self, username: str, token: str, address: str, on_msg=None, ignore_ssl_cert=False):
def __init__(self, username: str, token: str, address: str, on_msg=None, ignore_ssl_cert=False, stream=False):
self.username = username
self.token = token
self.address = address
Expand All @@ -28,6 +28,7 @@ def __init__(self, username: str, token: str, address: str, on_msg=None, ignore_
self.reid = self.REID()
self.running = False
self.ignore_ssl_cert = ignore_ssl_cert
self.stream = stream
setdefaulttimeout(60)

def send(self, data):
Expand Down Expand Up @@ -59,6 +60,16 @@ def _ready(self, ws):
print(f"Connected to {self.address}.")
self.running = True
self.lock.release()
if self.stream:
packet_start_stream = {
'REID': next(self.reid),
'AUTH': {'USER': self.username, 'TOKEN': self.token},
'VERB': 'STREAM',
'PATH': ['user', self.username, 'model'],
'META': {},
'PAYL': {}
}
self.ws.send(packb(packet_start_stream, use_bin_type=True), opcode=ABNF.OPCODE_BINARY)

def _handle_msg(self, ws, msg):
if isinstance(msg, bytes):
Expand All @@ -73,4 +84,4 @@ def construct_package(self, payload_data):
'PATH': ['user', self.username, 'model'],
'META': {},
'PAYL': payload_data
}
}
Empty file added pyghthouse/event/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions pyghthouse/event/event_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from .events import BaseEvent, KeyEvent
from queue import Queue, Empty


class EventManager:
queue: Queue[BaseEvent]

def __init__(self):
self.queue = Queue(maxsize=0)

def add_event(self, e: BaseEvent):
self.queue.put(e)

def add_key_event(self, code: int, down: bool):
self.add_event(KeyEvent(code, down))

def get_event(self):
try:
return self.queue.get(block=False)
except Empty:
return None

def get_all_events(self):
events = []
while not self.queue.empty():
events.append(self.queue.get(block=False))
return events
12 changes: 12 additions & 0 deletions pyghthouse/event/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from dataclasses import dataclass
from abc import ABC


class BaseEvent(ABC):
pass


@dataclass
class KeyEvent(BaseEvent):
code: int
down: bool
40 changes: 32 additions & 8 deletions pyghthouse/ph.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from pyghthouse.data.canvas import PyghthouseCanvas
from pyghthouse.connection.wsconnector import WSConnector
from pyghthouse.event.event_manager import EventManager


class VerbosityLevel(Enum):
Expand Down Expand Up @@ -43,9 +44,9 @@ class Pyghthouse:
Rate in 1/sec at which frames (images) are automatically sent to the lighthouse server. Also determines how
often the image_callback function is called.

image_callback: function (optional)
A function that takes no arguments and generates a valid lighthouse image (cf Image Format). If set, this
function is called before an image is sent and is used to determine said image.
image_callback: function List[Event] -> image (optional)
A function that takes an argument 'events' and generates a valid lighthouse image (cf Image Format). If set,
this function is called before an image is sent and is used to determine said image.
The function is guaranteed to be called frame_rate times each second *unless* its execution takes longer than
1/frame_rate seconds, in which case execution will be slowed down accordingly.

Expand All @@ -61,6 +62,10 @@ class Pyghthouse:
pyghthouse.VerbosityLevel.ALL:
Print all messages.

stream_remote_inputs: bool (optional, default: False)
Stream key events from the server. These can be retrieved via the get_event() or get_all_events()
methods and are passed to the callback method (if one exists).

Image Format
------------
Conceptually, each window of the highrise represents a pixel of a 28x14 RGB image.
Expand Down Expand Up @@ -141,9 +146,10 @@ class Pyghthouse:

class PHMessageHandler:

def __init__(self, verbosity=VerbosityLevel.WARN_ONCE):
def __init__(self, verbosity=VerbosityLevel.WARN_ONCE, event_manager=None):
self.verbosity = verbosity
self.warned_already = False
self.event_manager: EventManager = event_manager

def reset(self):
self.warned_already = False
Expand All @@ -152,6 +158,16 @@ def handle(self, msg):
if msg['RNUM'] == 200:
if self.verbosity == VerbosityLevel.ALL:
print(msg)
payload = msg['PAYL']
if isinstance(payload, dict):
try:
key = payload['key']
down = payload['dwn']
if self.event_manager is not None:
self.event_manager.add_key_event(key, down)
except KeyError:
pass

elif self.verbosity == VerbosityLevel.WARN:
self.print_warning(msg)
elif self.verbosity == VerbosityLevel.WARN_ONCE and not self.warned_already:
Expand Down Expand Up @@ -181,13 +197,13 @@ def run(self):
sleep_time = self.parent.send_interval - (time() % self.parent.send_interval)
sleep(sleep_time)
if self.parent.image_callback is not None:
image_from_callback = self.parent.image_callback()
image_from_callback = self.parent.image_callback(events=self.parent.get_all_events())
self.parent.set_image(image_from_callback)
self.parent.connector.send(self.parent.canvas.get_image_bytes())

def __init__(self, username: str, token: str, address: str = "wss://lighthouse.uni-kiel.de/websocket",
frame_rate: float = 30.0, image_callback=None, verbosity=VerbosityLevel.WARN_ONCE,
ignore_ssl_cert=False):
ignore_ssl_cert=False, stream_remote_inputs=False):
if frame_rate > 60.0 or frame_rate <= 0:
raise ValueError("Frame rate must be greater than 0 and at most 60.")
self.username = username
Expand All @@ -196,11 +212,13 @@ def __init__(self, username: str, token: str, address: str = "wss://lighthouse.u
self.send_interval = 1.0 / frame_rate
self.image_callback = image_callback
self.canvas = PyghthouseCanvas()
self.msg_handler = self.PHMessageHandler(verbosity)
self.event_manager = EventManager()
self.msg_handler = self.PHMessageHandler(verbosity, self.event_manager)
self.connector = WSConnector(username, token, address, on_msg=self.msg_handler.handle,
ignore_ssl_cert=ignore_ssl_cert)
ignore_ssl_cert=ignore_ssl_cert, stream=stream_remote_inputs)
self.config_lock = Lock()
self.ph_thread = None

signal(SIGINT, self._handle_sigint)

def connect(self):
Expand Down Expand Up @@ -253,3 +271,9 @@ def set_frame_rate(self, frame_rate):
def _handle_sigint(self, sig, frame):
self.close()
raise SystemExit(0)

def get_event(self):
return self.event_manager.get_event()

def get_all_events(self):
return self.event_manager.get_all_events()
Loading