Skip to content

Commit

Permalink
Merge pull request #7 from MattIPv4/web
Browse files Browse the repository at this point in the history
Implement web control panel
  • Loading branch information
MattIPv4 authored Oct 2, 2018
2 parents b052bdc + 7dfa732 commit 8ffdbeb
Show file tree
Hide file tree
Showing 24 changed files with 934 additions and 53 deletions.
9 changes: 9 additions & 0 deletions PyDMXControl/_Colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ def to_tuples(colors: List[int]) -> List[Tuple[str, int]]:
""" Assumes RGBWA """
return [(k, v) for k, v in Colors.to_dict(colors).items()]

@staticmethod
def to_hex(colors: List[int]) -> str:
def clamp(x): return max(0, min(x, 255))

result = "#"
for color in colors:
result += "{:02x}".format(clamp(color))
return result

@staticmethod
def to_print(colors: List[int], separator: str = ", ") -> str:
return separator.join([str(f) for f in colors])
Expand Down
18 changes: 16 additions & 2 deletions PyDMXControl/controllers/_Controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
"""

from time import sleep
from typing import Type, List, Union, Dict, Tuple
from typing import Type, List, Union, Dict, Tuple, Callable

from .utils.debug import Debugger
from .. import Colors
from ..profiles.defaults._Fixture import Channel, Fixture
from ..utils.exceptions import LTPCollisionException
from ..utils.timing import DMXMINWAIT, Ticker
from ..web import WebController


class Controller:
Expand Down Expand Up @@ -190,10 +191,18 @@ def clear_all_effects(self):
for fixture in self.get_all_fixtures():
fixture.clear_effects()

def debug_control(self, callbacks: dict = None):
def debug_control(self, callbacks: Dict[str, Callable] = None):
if callbacks is None: callbacks = {}
Debugger(self, callbacks).run()

def web_control(self, callbacks: Dict[str, Callable] = None):
if hasattr(self, "web"):
try:
self.web.stop()
except:
pass
self.web = WebController(self, callbacks)

def run(self, *args, **kwargs):
# Method used in transmitting controllers
pass
Expand All @@ -208,4 +217,9 @@ def close(self, *args, **kwargs):
fixture.clear_effects()
print("CLOSE: all effects cleared")

# Stop web
if hasattr(self, "web"):
self.web.stop()
print("CLOSE: web controller stopped")

return
9 changes: 5 additions & 4 deletions PyDMXControl/controllers/utils/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
from collections import namedtuple
from inspect import signature, Parameter
from re import compile
from typing import List, Tuple, Union
from typing import List, Tuple, Union, Dict, Callable

from ... import Colors
from ...profiles.defaults import Fixture, Vdim


class Debugger:

def __init__(self, controller: 'Controller', callbacks: dict = None):
def __init__(self, controller: 'Controller', callbacks: Dict[str, Callable] = None):
self.cont = controller
self.cbs = {} if callbacks is None else callbacks

Expand All @@ -24,7 +24,7 @@ def __default_callbacks(self):
if not 'all_on' in self.cbs: self.cbs['all_on'] = self.cont.all_on
if not 'on' in self.cbs: self.cbs['on'] = self.cont.all_on

if not 'all_off' in self.cbs: self.cbs['all_of'] = self.cont.all_off
if not 'all_off' in self.cbs: self.cbs['all_off'] = self.cont.all_off
if not 'off' in self.cbs: self.cbs['off'] = self.cont.all_off

if not 'all_locate' in self.cbs: self.cbs['all_locate'] = self.cont.all_locate
Expand Down Expand Up @@ -178,7 +178,8 @@ def run_fixture(self):
# Apply
value = int(value)
fixture.set_channel(channel, value)
print("[Channel Debug] Channel '" + channel + "' set to " + fixture.get_channel_value(channel))
print("[Channel Debug] Channel '" + channel + "' set to " + str(
self.__fixture_channel_value(fixture, channel)))
continue

# Channel list
Expand Down
64 changes: 50 additions & 14 deletions PyDMXControl/profiles/defaults/_Fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,7 @@ def __init__(self, start_channel, *, name: str = ""):
self.__channel_aliases = {}

def __str__(self):
return "Fixture #{} ('{}') of type {} using channels {}->{} ({}).".format(
self.id,
self.name,
self.__class__.__name__,
self.__start_channel,
(self.__start_channel + len(self.__channels) - 1),
len(self.__channels)
)
return self.title

# Internal

Expand Down Expand Up @@ -107,10 +100,10 @@ def _set_name(self, name: str) -> None:

# Properties

@staticmethod
def _valid_channel_value(value: int) -> bool:
def _valid_channel_value(self, value: int, channel: Union[str, int]) -> bool:
if value < 0 or value > 255:
warn('DMX value must be between 0 and 255.')
warn('{} DMX value must be between 0 and 255. Received value {} for channel {}'.format(
self.title, value, channel))
return False
return True

Expand All @@ -122,6 +115,10 @@ def id(self) -> int:
def name(self) -> str:
return self.__name

@property
def start_channel(self) -> int:
return self.__start_channel

@property
def next_channel(self) -> int:
return len(self.__channels) + 1
Expand All @@ -130,9 +127,26 @@ def next_channel(self) -> int:
def channels(self) -> dict:
channels = {}
for i, chan in enumerate(self.__channels):
channels[self.__start_channel + i] = {'name': chan.name, 'value': self.get_channel_value(i)}
channels[self.start_channel + i] = {'name': chan.name, 'value': self.get_channel_value(i)}
return channels

@property
def channel_usage(self) -> str:
return "{}->{} ({})".format(
self.start_channel,
(self.start_channel + len(self.__channels) - 1),
len(self.__channels)
)

@property
def title(self) -> str:
return "Fixture #{} ('{}') of type {} using channels {}.".format(
self.id,
self.name,
self.__class__.__name__,
self.channel_usage
)

# Channels

def get_channel_id(self, channel: Union[str, int]) -> int:
Expand All @@ -154,11 +168,12 @@ def get_channel_id(self, channel: Union[str, int]) -> int:
return -1

def get_channel_value(self, channel: int) -> Tuple[int, datetime]:
if channel >= len(self.__channels): return -1, datetime.utcnow()
channel = self.get_channel_id(channel)
if channel >= len(self.__channels) or channel < 0: return -1, datetime.utcnow()
return self.__channels[channel].get_value()

def set_channel(self, channel: [str, int], value: int) -> 'Fixture':
if not self._valid_channel_value(value):
if not self._valid_channel_value(value, channel):
return self

channel = self.get_channel_id(channel)
Expand Down Expand Up @@ -274,6 +289,27 @@ def color(self, color: Union[Colors, List[int], Tuple[int], str], milliseconds:
# Apply
self.anim(milliseconds, *color)

def get_color(self) -> Union[None, List[int]]:
red = self.get_channel_value(self.get_channel_id("r"))
green = self.get_channel_value(self.get_channel_id("g"))
blue = self.get_channel_value(self.get_channel_id("b"))
white = self.get_channel_value(self.get_channel_id("w"))
amber = self.get_channel_value(self.get_channel_id("a"))
if red[0] == -1 or green[0] == -1 or blue[0] == -1:
return None
color = [red[0], green[0], blue[0]]
if white[0] != -1:
color.append(white[0])
if amber[0] != -1:
color.append(amber[0])
return color

def on(self):
self.dim(255)

def off(self):
self.dim(0)

def locate(self):
self.color([255, 255, 255, 255, 255])
self.dim(255)
19 changes: 17 additions & 2 deletions PyDMXControl/profiles/defaults/_Vdim.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

from datetime import datetime
from typing import Union, Tuple
from typing import Union, Tuple, List

from ._Fixture import Fixture

Expand Down Expand Up @@ -65,7 +65,22 @@ def set_channel(self, channel: Union[str, int], value: int) -> Fixture:

def set_vdim(self, value: int) -> Fixture:
# Update the vdim value
if not self._valid_channel_value(value): return self
if not self._valid_channel_value(value, 'vdim'): return self
self.__vdim = value
self.__vdimUpdated = datetime.utcnow()
return self

def get_color(self) -> Union[None, List[int]]:
red = self.get_channel_value(self.get_channel_id("r"), False)
green = self.get_channel_value(self.get_channel_id("g"), False)
blue = self.get_channel_value(self.get_channel_id("b"), False)
white = self.get_channel_value(self.get_channel_id("w"), False)
amber = self.get_channel_value(self.get_channel_id("a"), False)
if red[0] == -1 or green[0] == -1 or blue[0] == -1:
return None
color = [red[0], green[0], blue[0]]
if white[0] != -1:
color.append(white[0])
if amber[0] != -1:
color.append(amber[0])
return color
17 changes: 17 additions & 0 deletions PyDMXControl/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
* PyDMXControl: A Python 3 module to control DMX via Python. Featuring fixture profiles and working with uDMX.
* <https://github.com/MattIPv4/PyDMXControl/>
* Copyright (C) 2018 Matt Cowley (MattIPv4) (me@mattcowley.co.uk)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, please see
* <https://github.com/MattIPv4/PyDMXControl/blob/master/LICENSE> or <http://www.gnu.org/licenses/>.
"""
19 changes: 19 additions & 0 deletions PyDMXControl/web/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
* PyDMXControl: A Python 3 module to control DMX via Python. Featuring fixture profiles and working with uDMX.
* <https://github.com/MattIPv4/PyDMXControl/>
* Copyright (C) 2018 Matt Cowley (MattIPv4) (me@mattcowley.co.uk)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, please see
* <https://github.com/MattIPv4/PyDMXControl/blob/master/LICENSE> or <http://www.gnu.org/licenses/>.
"""

from ._webController import WebController
Loading

0 comments on commit 8ffdbeb

Please sign in to comment.