wip: add control script
Signed-off-by: Thomas Schmid <tom@lfence.de>
This commit is contained in:
parent
ade567081c
commit
ebca53d6cf
326
interactive.py
Normal file
326
interactive.py
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
from threading import Event, Condition
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import urwid
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
|
||||||
|
from urwid import signals
|
||||||
|
from urwid.util import _tagmarkup_recurse
|
||||||
|
|
||||||
|
client = None
|
||||||
|
CONFIG = dict()
|
||||||
|
x = 0
|
||||||
|
y = 0
|
||||||
|
c = 0
|
||||||
|
|
||||||
|
def labled_widget(label, widget):
|
||||||
|
return urwid.Columns([
|
||||||
|
urwid.Text(label),
|
||||||
|
widget
|
||||||
|
])
|
||||||
|
|
||||||
|
class GridFlowMenu(urwid.WidgetWrap):
|
||||||
|
def __init__(self, choices, callback):
|
||||||
|
self.buttons = dict()
|
||||||
|
for choice in choices:
|
||||||
|
b = urwid.Button(choice)
|
||||||
|
self.buttons[choice] = b
|
||||||
|
signals.connect_signal(b, "click", callback, choice)
|
||||||
|
super().__init__(urwid.GridFlow(self.buttons.values(), 12, 1, 1, "center"))
|
||||||
|
|
||||||
|
class Slider(urwid.Widget):
|
||||||
|
_selectable=True
|
||||||
|
_sizing = frozenset(["flow"])
|
||||||
|
|
||||||
|
def __init__(self, value = 0, max = 127, min = 0, step = 1):
|
||||||
|
signals.register_signal(self.__class__, ["value_changed"])
|
||||||
|
if max <= min or min >= max:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
self.max = max
|
||||||
|
self.min = min
|
||||||
|
self.step = step
|
||||||
|
self.last_focus = False
|
||||||
|
self.input_digits = ""
|
||||||
|
self.set_value(value)
|
||||||
|
|
||||||
|
def set_value(self, value, emit=False):
|
||||||
|
self.value = min(max(value, self.min), self.max)
|
||||||
|
self.ratio = float(value-self.min)/float(self.max - self.min)
|
||||||
|
self.ratio = min(max(self.ratio, 0.0), 1.0)
|
||||||
|
if emit:
|
||||||
|
self._emit("value_changed", self.value)
|
||||||
|
self._invalidate()
|
||||||
|
|
||||||
|
def rows(self, size, focus=False):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def keypress(self, size, key):
|
||||||
|
if key == "left":
|
||||||
|
self.set_value(self.value - self.step, True)
|
||||||
|
elif key == "right":
|
||||||
|
self.set_value(self.value + self.step, True)
|
||||||
|
elif key.isdigit():
|
||||||
|
self.input_digits += key
|
||||||
|
self.set_value(int(self.input_digits), True)
|
||||||
|
else:
|
||||||
|
return key
|
||||||
|
|
||||||
|
def lost_focus(self):
|
||||||
|
self.input_digits = ""
|
||||||
|
|
||||||
|
def render(self, size, focus=False):
|
||||||
|
if not focus and self.last_focus:
|
||||||
|
self.lost_focus()
|
||||||
|
|
||||||
|
if focus != self.last_focus:
|
||||||
|
self.last_focus = focus
|
||||||
|
|
||||||
|
(maxcol,) = size
|
||||||
|
|
||||||
|
col1 = int(float(maxcol) * self.ratio)
|
||||||
|
col2 = int(maxcol - col1)
|
||||||
|
|
||||||
|
return urwid.CanvasJoin([
|
||||||
|
(urwid.SolidCanvas(chr(0x2593) if not focus else chr(9608), col1, 1), None, False, col1),
|
||||||
|
(urwid.SolidCanvas(chr(9472) if not focus else '>', col2, 1), None, False, col2)
|
||||||
|
])
|
||||||
|
|
||||||
|
class LabelSlider(urwid.WidgetWrap):
|
||||||
|
def __init__(self, min = 0, max = 100, value = 0, step = 1, value_format="{:3d}"):
|
||||||
|
self.slider = Slider(value = value, max = max, min = min, step = step)
|
||||||
|
self.value_format = value_format
|
||||||
|
self.text = urwid.Text(value_format.format(value))
|
||||||
|
signals.connect_signal(self.slider, "value_changed", self.slider_changed)
|
||||||
|
super().__init__(urwid.Columns([self.slider, ('pack', self.text)], focus_column=0, dividechars = 2))
|
||||||
|
|
||||||
|
def render_text(self, value):
|
||||||
|
self.text.set_text(self.value_format.format(value))
|
||||||
|
|
||||||
|
def slider_changed(self, slider, value):
|
||||||
|
self.render_text(value)
|
||||||
|
|
||||||
|
def set_value(self, value, emit=False):
|
||||||
|
self.slider.set_value(value, emit=False)
|
||||||
|
self.text.render_text
|
||||||
|
|
||||||
|
def get_slider(self):
|
||||||
|
return self.slider
|
||||||
|
|
||||||
|
class myFrame(urwid.Frame):
|
||||||
|
def keypress(self, size, key):
|
||||||
|
if key == "tab":
|
||||||
|
next_focus = "footer" if self.focus_position == "body" else "body"
|
||||||
|
self.set_focus(next_focus)
|
||||||
|
else:
|
||||||
|
return super().keypress(size, key)
|
||||||
|
|
||||||
|
class View:
|
||||||
|
def __init__(self, laser):
|
||||||
|
self.channel_view = self.build_channel_box()
|
||||||
|
self.main_view = self.build_main_box()
|
||||||
|
self.frame = myFrame(body=self.channel_view, footer=GridFlowMenu(choices=["Main", "Channels"], callback= self.change_view))
|
||||||
|
#fill = urwid.Filler(frame, "top")
|
||||||
|
self.loop = urwid.MainLoop(self.frame)
|
||||||
|
|
||||||
|
def change_view(self, button, choice):
|
||||||
|
if choice == "Main":
|
||||||
|
self.frame.set_body(self.main_view)
|
||||||
|
elif choice == "Channels":
|
||||||
|
self.frame.set_body(self.channel_view)
|
||||||
|
|
||||||
|
def mode_select_callback(self, radiobutton, state):
|
||||||
|
if radiobutton.state:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def build_main_box(self):
|
||||||
|
fill = urwid.Filler(urwid.Text("Hello World", "left"))
|
||||||
|
|
||||||
|
self.mode_rb = []
|
||||||
|
mode = labled_widget("Mode", urwid.Pile([
|
||||||
|
urwid.RadioButton(self.mode_rb, "Manual"),
|
||||||
|
urwid.RadioButton(self.mode_rb, "Automatic")
|
||||||
|
]))
|
||||||
|
|
||||||
|
for e in self.mode_rb:
|
||||||
|
signals.connect_signal(e, "change", self.mode_select_callback)
|
||||||
|
|
||||||
|
self.effect_rb =[]
|
||||||
|
effect = labled_widget("Effect:", urwid.Pile([
|
||||||
|
urwid.RadioButton(self.effect_rb, "Effect 1"),
|
||||||
|
urwid.RadioButton(self.effect_rb, "Effect 2"),
|
||||||
|
urwid.RadioButton(self.effect_rb, "Effect 3"),
|
||||||
|
urwid.RadioButton(self.effect_rb, "Effect 4")
|
||||||
|
]))
|
||||||
|
|
||||||
|
fill = urwid.Filler(urwid.Pile([mode, urwid.Divider(), effect]))
|
||||||
|
return urwid.LineBox(fill, title="Main")
|
||||||
|
|
||||||
|
def build_channel_box(self):
|
||||||
|
self.mode = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.animation = LabelSlider(min = 0, max=0xFF)
|
||||||
|
self.strobe = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.effect = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.x = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.y = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.zoom = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.color = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.reset = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
self.color_effect = LabelSlider(min = 0, max = 0xFF)
|
||||||
|
|
||||||
|
signals.connect_signal(self.mode.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.animation.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.strobe.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.effect.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.x.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.y.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.zoom.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.color.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.reset.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
signals.connect_signal(self.color_effect.get_slider(), "value_changed", self.value_changed_callback)
|
||||||
|
|
||||||
|
self.labels = urwid.Pile([
|
||||||
|
urwid.Text("Mode:"),
|
||||||
|
urwid.Text("Animation:"),
|
||||||
|
urwid.Text("Strobe:"),
|
||||||
|
urwid.Text("Effect:"),
|
||||||
|
urwid.Text("X:"),
|
||||||
|
urwid.Text("Y:"),
|
||||||
|
urwid.Text("Zoom:"),
|
||||||
|
urwid.Text("Color:"),
|
||||||
|
urwid.Text("Reset:"),
|
||||||
|
urwid.Text("Color-Effect:")]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.values = urwid.Pile([
|
||||||
|
self.mode,
|
||||||
|
self.animation,
|
||||||
|
self.strobe,
|
||||||
|
self.effect,
|
||||||
|
self.x,
|
||||||
|
self.y,
|
||||||
|
self.zoom,
|
||||||
|
self.color,
|
||||||
|
self.reset,
|
||||||
|
self.color_effect
|
||||||
|
])
|
||||||
|
|
||||||
|
grid = urwid.Columns([('weight', 0.25, self.labels), self.values])
|
||||||
|
return urwid.LineBox(urwid.Filler(grid), title="Channels")
|
||||||
|
|
||||||
|
def value_changed_callback(self, slider, value):
|
||||||
|
if slider == self.x or slider == self.y:
|
||||||
|
move_laser(client, (self.x.value, self.y.value))
|
||||||
|
elif slider == self.mode:
|
||||||
|
pass
|
||||||
|
elif slider == self.animation:
|
||||||
|
pass
|
||||||
|
elif slider == self.strobe:
|
||||||
|
pass
|
||||||
|
elif slider == self.effect:
|
||||||
|
pass
|
||||||
|
elif slider == self.zoom:
|
||||||
|
pass
|
||||||
|
elif slider == self.color:
|
||||||
|
pass
|
||||||
|
elif slider == self.reset:
|
||||||
|
pass
|
||||||
|
elif slider == self.color_effect:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_channels(self, channels):
|
||||||
|
self.animation.set_value(channels[1])
|
||||||
|
self.strobe.set_value(channels[2])
|
||||||
|
self.effect.set_value(channels[3])
|
||||||
|
self.x.set_value(channels[4])
|
||||||
|
self.y.set_value(channels[5])
|
||||||
|
self.zoom.set_value(channels[6])
|
||||||
|
self.color.set_value(channels[7])
|
||||||
|
self.reset.set_value(channels[8])
|
||||||
|
self.color_effect.set_value(channels[9])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.loop.draw_screen()
|
||||||
|
|
||||||
|
view = View(client)
|
||||||
|
|
||||||
|
def move_laser(client, pos):
|
||||||
|
cmd = {"laser": {
|
||||||
|
"pos": {"x": pos[0], "y": pos[1]}
|
||||||
|
}}
|
||||||
|
client.publish("kitchen/laser/state/cmd", payload = json.dumps(cmd))
|
||||||
|
|
||||||
|
def on_state_changed(client, user_data, message):
|
||||||
|
data = json.loads(message.payload)
|
||||||
|
|
||||||
|
if "channels" in data:
|
||||||
|
view.update_channels(data["channels"])
|
||||||
|
|
||||||
|
def unhandled_input(input):
|
||||||
|
global x, y
|
||||||
|
if input == "up":
|
||||||
|
y = y + 1
|
||||||
|
move_laser(client, (x, y))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if input == "down":
|
||||||
|
y = y - 1
|
||||||
|
move_laser(client, (x, y))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if input == "left":
|
||||||
|
x = x - 1
|
||||||
|
move_laser(client, (x, y))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if input == "right":
|
||||||
|
x = x + 1
|
||||||
|
move_laser(client, (x, y))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if input == "c":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def exit_app(sig, frame):
|
||||||
|
client.publish("kitchen/laser/state/cmd", payload=json.dumps({"mode": "automatic"}))
|
||||||
|
client.loop_stop()
|
||||||
|
client.disconnect()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
global client
|
||||||
|
client = mqtt.Client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.connect(args.host)
|
||||||
|
except:
|
||||||
|
print("unable to connect to mqtt broker")
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
client.subscribe("kitchen/laser/state")
|
||||||
|
client.message_callback_add("kitchen/laser/state", on_state_changed)
|
||||||
|
client.loop_start()
|
||||||
|
client.publish("kitchen/laser/state/cmd", payload=json.dumps({"mode": "manual"}))
|
||||||
|
|
||||||
|
view.loop.unhandled_input = unhandled_input
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, exit_app)
|
||||||
|
|
||||||
|
try:
|
||||||
|
view.loop.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("test")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--host", default="hasspi")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
main(args)
|
Loading…
Reference in New Issue
Block a user