Files
controller-wisetopdataserver/python/biopro/impl/led.py
T
2021-12-20 14:52:55 +08:00

522 lines
15 KiB
Python

import abc
from math import nan
from typing import Optional, List, Iterable, Union, Dict, Tuple
from biopro.util.cli import *
from biopro.util.console import hex_line, hex_value
from .gpio import P3Pin
from .interface_spi import SoftwareImplSpiInterface
class LEDParameter:
__slots__ = ('flash', 'red', 'green', 'blue')
def __init__(self):
self.flash: float = 1.0
self.red: Optional[float] = None
self.green: Optional[float] = None
self.blue: Optional[float] = None
def set_color(self,
flash: Optional[float] = nan,
red: Optional[float] = nan,
green: Optional[float] = nan,
blue: Optional[float] = nan) -> 'LEDParameter':
"""
*flash*, *red*, *green* and *blue* should be a floating number locate in [0.0, 1.0] and ``None`` and ``NaN``.
``None`` value meaning the value follow the previous keyframe.
``NaN`` value meaning do not change current value.
:param flash:
:param red:
:param green:
:param blue:
:return:
"""
if red == red:
self.red = float(red) if red is not None else None
if green == green:
self.green = float(green) if green is not None else None
if blue == blue:
self.blue = float(blue) if blue is not None else None
if flash == flash:
self.flash = float(flash) if flash is not None else None
return self
def set_color_by_parameter(self, para: 'LEDParameter') -> 'LEDParameter':
self.flash = para.flash
self.red = para.red
self.green = para.green
self.blue = para.blue
return self
def to_byte(self, prev: Optional[bytes], global_flash: float = 1.0) -> bytes:
f = self.flash if self.flash is not None else 1.0
b = prev[1] if prev else 0
g = prev[2] if prev else 0
r = prev[3] if prev else 0
return bytes([0xE0 | int(0x1F * f * global_flash) & 0x1F,
int(0xFF * self.blue) & 0xFF if self.blue is not None else b,
int(0xFF * self.green) & 0xFF if self.green is not None else g,
int(0xFF * self.red) & 0xFF if self.red is not None else r])
def __str__(self):
return '(%s,%s,%s,%s)' % (
'%02X' % (int(0x1F * self.flash) & 0x1F) if self.flash is not None else 'N',
'%02X' % (int(0xFF * self.red) & 0xFF) if self.red is not None else 'N',
'%02X' % (int(0xFF * self.green) & 0xFF) if self.green is not None else 'N',
'%02X' % (int(0xFF * self.blue) & 0xFF) if self.blue is not None else 'N',
)
class AbstractLEDKeyFrame(metaclass=abc.ABCMeta):
__slots__ = ('time',)
def __init__(self, time: float):
self.time: float = time
@abc.abstractmethod
def foreach(self, total: int) -> Iterable[Optional[LEDParameter]]:
pass
def __repr__(self):
return str(self)
def __str__(self):
return '[%.4f]' % self.time
class AllLEDKeyFrame(AbstractLEDKeyFrame):
__slots__ = ('parameter',)
def __init__(self, time: float):
super().__init__(time)
self.parameter = LEDParameter()
def foreach(self, total: int) -> Iterable[LEDParameter]:
for _ in range(total):
yield self.parameter
def set_color(self,
flash: Optional[float] = nan,
red: Optional[float] = nan,
green: Optional[float] = nan,
blue: Optional[float] = nan) -> 'AllLEDKeyFrame':
self.parameter.set_color(flash, red, green, blue)
return self
def set_color_by_frame(self, frame: Union[LEDParameter, 'AllLEDKeyFrame']) -> 'AllLEDKeyFrame':
if isinstance(frame, LEDParameter):
self.parameter.set_color_by_parameter(frame)
elif isinstance(frame, AllLEDKeyFrame):
self.parameter.set_color_by_parameter(frame.parameter)
return self
@classmethod
def new(cls,
time: float,
flash: Optional[float] = None,
red: Optional[float] = None,
green: Optional[float] = None,
blue: Optional[float] = None) -> 'AllLEDKeyFrame':
ret = AllLEDKeyFrame(time)
ret.parameter.set_color(flash, red, green, blue)
return ret
def __str__(self):
return super().__str__() + str(self.parameter)
class LEDKeyFrame(AbstractLEDKeyFrame):
__slots__ = ('parameter',)
def __init__(self, time: float):
super().__init__(time)
self.parameter: Dict[int, LEDParameter] = {}
def foreach(self, total: int) -> Iterable[Optional[LEDParameter]]:
for i in range(total):
yield self.parameter.get(i, None)
def __contains__(self, index: int) -> bool:
return index in self.parameter
def __getitem__(self, index: int) -> Optional[LEDParameter]:
return self.parameter.get(index, None)
def __setitem__(self, index: int, value: Union[LEDParameter, float, Tuple[float, ...]]):
if isinstance(value, LEDParameter):
self.parameter[index] = value
elif isinstance(value, float):
p = self.parameter.get(index, None)
if p is None:
p = LEDParameter()
self.parameter[index] = p
p.flash = value
else:
p = self.parameter.get(index, None)
if p is None:
p = LEDParameter()
self.parameter[index] = p
p.set_color(*value)
def __delitem__(self, index: int):
try:
del self.parameter[index]
except KeyError:
pass
def set_color(self,
led: int,
flash: Optional[float] = nan,
red: Optional[float] = nan,
green: Optional[float] = nan,
blue: Optional[float] = nan) -> 'LEDKeyFrame':
p = self.parameter.get(led, None)
if p is None:
p = LEDParameter()
self.parameter[led] = p
p.set_color(flash, red, green, blue)
return self
def set_color_by_frame(self,
led: int,
frame: Union[None, LEDParameter, AllLEDKeyFrame, 'LEDKeyFrame']) -> 'LEDKeyFrame':
if frame is None:
return self
elif isinstance(frame, LEDKeyFrame):
for _led, _p in frame.parameter.items():
self.set_color_by_frame(_led, _p)
else:
p = self.parameter.get(led, None)
if p is None:
p = LEDParameter()
self.parameter[led] = p
if isinstance(frame, LEDParameter):
p.set_color_by_parameter(frame)
elif isinstance(frame, AllLEDKeyFrame):
p.set_color_by_parameter(frame.parameter)
return self
def __str__(self):
return super().__str__() + '{' + ','.join(map(lambda i, p: '%d:%s' % (i, str(p)), self.parameter.items())) + '}'
class LED:
"""
**LED device**
**LED state**
1. power on
2. on connected
3. on working
4. on error
5. low battery
**color present**
(red, green, blue, flush_interval)
"""
COLOR_RED = AllLEDKeyFrame.new(0., 1., 1., 0., 0.)
COLOR_ORANGE = AllLEDKeyFrame.new(0., 1., 1., .3, 0.)
COLOR_YELLOW = AllLEDKeyFrame.new(0., 1., .5, .5, 0.)
COLOR_GREEN = AllLEDKeyFrame.new(0., 1., 0., 1., 0.)
COLOR_BLUE = AllLEDKeyFrame.new(0., 1., 0., 0., 1.)
COLOR_CYAN = AllLEDKeyFrame.new(0., 1., 0., 1., 1.)
COLOR_MAGENTA = AllLEDKeyFrame.new(0., 1.0, 1., 0., 1.)
COLOR_WHITE = AllLEDKeyFrame.new(0., 1., .5, .9, .7)
COLOR_BLACK = AllLEDKeyFrame.new(0., 0., 0., 0., 0.)
LIGHT_OFF = COLOR_BLACK
POWER_ON = 'power_on'
POWER_OFF = 'power_off'
AVAILABLE = 'available'
CONNECTED = 'connected'
ERROR = 'error'
UPS_USING = 'ups_using'
COLOR = {
'red': COLOR_RED,
'r': COLOR_RED,
'orange': COLOR_ORANGE,
'o': COLOR_ORANGE,
'yellow': COLOR_YELLOW,
'y': COLOR_YELLOW,
'green': COLOR_GREEN,
'g': COLOR_GREEN,
'blue': COLOR_BLUE,
'b': COLOR_BLUE,
'cyan': COLOR_CYAN,
'c': COLOR_CYAN,
'magenta': COLOR_MAGENTA,
'm': COLOR_MAGENTA,
'white': COLOR_WHITE,
'w': COLOR_WHITE,
'off': LIGHT_OFF,
'k': LIGHT_OFF,
'f0': AllLEDKeyFrame.new(0.0, 0.0, None, None, None),
'f1': AllLEDKeyFrame.new(0.0, 0.1, None, None, None),
'f2': AllLEDKeyFrame.new(0.0, 0.2, None, None, None),
'f3': AllLEDKeyFrame.new(0.0, 0.3, None, None, None),
'f4': AllLEDKeyFrame.new(0.0, 0.4, None, None, None),
'f5': AllLEDKeyFrame.new(0.0, 0.5, None, None, None),
'f6': AllLEDKeyFrame.new(0.0, 0.6, None, None, None),
'f7': AllLEDKeyFrame.new(0.0, 0.7, None, None, None),
'f8': AllLEDKeyFrame.new(0.0, 0.8, None, None, None),
'f9': AllLEDKeyFrame.new(0.0, 0.9, None, None, None),
'F': AllLEDKeyFrame.new(0.0, 1.0, None, None, None),
POWER_ON: (AllLEDKeyFrame(0.5).set_color_by_frame(COLOR_GREEN),
AllLEDKeyFrame(0.5).set_color_by_frame(COLOR_BLACK)),
POWER_OFF: COLOR_BLACK,
AVAILABLE: COLOR_GREEN,
CONNECTED: COLOR_CYAN,
UPS_USING: (AllLEDKeyFrame(1.2).set_color_by_frame(COLOR_ORANGE),
AllLEDKeyFrame(0.8).set_color_by_frame(COLOR_BLACK)),
ERROR: (AllLEDKeyFrame(0.5).set_color_by_frame(COLOR_RED),
AllLEDKeyFrame(0.5).set_color_by_frame(COLOR_BLACK)),
}
_INS_START = bytes([0x00, 0x00, 0x00, 0x00])
_INS_STOP = bytes([0xFF, 0xFF, 0xFF, 0xFF])
_INS_OFF = bytes([0xE0, 0x00, 0x00, 0x00])
__slots__ = ('_prev', '_spi', '_total', '_flash')
def __init__(self, total: int, interface: Optional[SoftwareImplSpiInterface]):
self._total = total
self._prev: List[Optional[bytes]] = [None for _ in range(total)]
self._spi = interface
self._flash = 1.0
@property
def global_flash(self) -> float:
return self._flash
@global_flash.setter
def global_flash(self, value: float):
self._flash = value
def update(self, keyframe: AbstractLEDKeyFrame):
if self._spi is not None:
with self._spi as spi:
spi.send_byte_transmit(self._INS_START)
for i, n in enumerate(keyframe.foreach(self._total)):
b = n.to_byte(self._prev[i], global_flash=self._flash)
spi.send_byte_transmit(b)
self._prev[i] = b
spi.send_byte_transmit(self._INS_STOP)
def light_off(self):
if self._spi is not None:
with self._spi as spi:
spi.send_byte_transmit(self._INS_START)
for _ in range(self._total):
spi.send_byte_transmit(self._INS_OFF)
spi.send_byte_transmit(self._INS_STOP)
# noinspection PyUnusedLocal
class Main(CliReplMain):
"""LED control"""
def __init__(self):
super().__init__()
self.count = 16
self._spi: Optional[SoftwareImplSpiInterface] = None
self._led: Optional[LED] = None
@cli_flags('-h', '--help', force_return=True)
def _help(self, opt: str):
"""print help document"""
self.print_help()
@cli_options('-c', value='COUNT')
def _count(self, opt: str, value: str):
"""led count"""
self.count = int(value)
def __enter__(self):
self._spi = SoftwareImplSpiInterface(P3Pin.LED_SCL,
P3Pin.LED_SDA,
cs=33)
self._spi.select = 33
self._led = LED(self.count, self._spi)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._led is not None:
self._led.light_off()
if self._spi is not None:
self._spi.close()
self._spi: Optional[SoftwareImplSpiInterface] = None
self._led: Optional[LED] = None
@cli_command('h', 'help')
def _help_c(self, cmd: str, argv: List[str]):
"""print help document"""
self.print_help()
@cli_command('send')
def _send(self, cmd: str, argv: List[str]):
""""""
send = bytes([hex_value(v) for v in argv])
self._spi.send_byte(send)
recv = self._spi.recv_byte(len(send))
if recv is not None:
print(hex_line(recv))
@cli_command('flash')
def _flash(self, cmd: str, argv: List[str]):
"""set global flash"""
if len(argv) == 0:
print(self._led.global_flash)
else:
self._led.global_flash = float(argv[0])
@cli_command('color')
def _frame(self, cmd: str, argv: List[str]):
"""set led color"""
if len(argv) == 0:
print(' '.join(sorted(LED.COLOR.keys())))
elif len(argv) == 1:
frame = LED.COLOR[argv[0]]
self._led.update(frame)
else:
frame = AllLEDKeyFrame(0)
frame.set_color(float(argv[3]) if len(argv) > 3 else None,
float(argv[0]), float(argv[1]), float(argv[2]))
self._led.update(frame)
@cli_command('play')
def _play(self, cmd: str, argv: List[str]):
"""play led"""
a = Arguments(argv)
if a.has_opt('-i'):
interval = a.get_opt('-i', cast=float)
else:
interval = 1
repeat = a.has_opt('-r')
frame = [
AllLEDKeyFrame(interval).set_color_by_frame(LED.COLOR[c]) for i, c in enumerate(a)
]
self._play.track = frame
try:
self._play.play(repeat=repeat)
except KeyboardInterrupt:
pass
finally:
self._led.light_off()
@cli_command('off')
def _off(self, cmd: str, argv: List[str]):
"""light off"""
self._led.light_off()
# noinspection PyUnusedLocal
class LEDControlMain(CliSubCommandMain):
def __init__(self, command: str):
super().__init__(command)
self.count = 16
self.state: Optional[str] = None
self.finalize = False
@cli_flags('-h', '--help', force_return=True)
def _help(self, opt: str):
"""print help document"""
self.print_help()
@cli_options('-c', value='COUNT')
def _count(self, opt: str, value: str):
"""led count"""
self.count = int(value)
@cli_flags('-F', '--finalize')
def _finalize(self, opt: str):
"""finalize resource"""
self.finalize = True
@cli_arguments(0, value='STATE')
def _state(self, pos: int, value: str):
"""LED state"""
self.state = value
def run(self):
if self.state is None:
print(' , '.join(sorted(LED.COLOR.keys())))
return
key_frame = LED.COLOR[self.state]
if not isinstance(key_frame, AbstractLEDKeyFrame):
raise ValueError()
spi = SoftwareImplSpiInterface(P3Pin.LED_SCL, P3Pin.LED_SDA, cs=33)
spi.select = 33
led = LED(self.count, spi)
try:
led.update(key_frame)
finally:
if self.finalize:
led.light_off()
spi.close()
if __name__ == '__main__':
Main().main()