Refactored PCA9685 backend

This commit is contained in:
Fabio Manganiello 2021-05-17 15:32:43 +02:00
parent fc1d9ad3e6
commit a39452124d
2 changed files with 59 additions and 56 deletions

View file

@ -146,10 +146,10 @@ class JoystickLinuxBackend(Backend):
def run(self): def run(self):
super().run() super().run()
self.logger.info(f'Opening {self.device}...')
while not self.should_stop(): while not self.should_stop():
# Open the joystick device. # Open the joystick device.
self.logger.info(f'Opening {self.device}...')
try: try:
jsdev = open(self.device, 'rb') jsdev = open(self.device, 'rb')
self._init_joystick(jsdev) self._init_joystick(jsdev)
@ -165,13 +165,16 @@ class JoystickLinuxBackend(Backend):
if evbuf: if evbuf:
_, value, evt_type, number = struct.unpack('IhBB', evbuf) _, value, evt_type, number = struct.unpack('IhBB', evbuf)
if evt_type & 0x80: # Initial state notification
continue
if evt_type & 0x01: if evt_type & 0x01:
button = self._button_map[number] button = self._button_map[number]
if button: if button:
self._button_states[button] = value self._button_states[button] = value
evt_class = JoystickButtonPressedEvent if value else JoystickButtonReleasedEvent evt_class = JoystickButtonPressedEvent if value else JoystickButtonReleasedEvent
# noinspection PyTypeChecker # noinspection PyTypeChecker
self.bus.post(evt_class(button=button)) self.bus.post(evt_class(device=self.device, button=button))
if evt_type & 0x02: if evt_type & 0x02:
axis = self._axis_map[number] axis = self._axis_map[number]
@ -179,7 +182,7 @@ class JoystickLinuxBackend(Backend):
fvalue = value / 32767.0 fvalue = value / 32767.0
self._axis_states[axis] = fvalue self._axis_states[axis] = fvalue
# noinspection PyTypeChecker # noinspection PyTypeChecker
self.bus.post(JoystickAxisEvent(axis=axis, value=fvalue)) self.bus.post(JoystickAxisEvent(device=self.device, axis=axis, value=fvalue))
except OSError as e: except OSError as e:
self.logger.warning(f'Connection to {self.device} lost: {e}') self.logger.warning(f'Connection to {self.device} lost: {e}')
self.bus.post(JoystickDisconnectedEvent(device=self.device)) self.bus.post(JoystickDisconnectedEvent(device=self.device))

View file

@ -1,5 +1,5 @@
import time import time
from typing import Optional, Dict from typing import Optional, Dict, Iterable
from platypush.plugins import Plugin, action from platypush.plugins import Plugin, action
@ -30,83 +30,82 @@ class PwmPca9685Plugin(Plugin):
This plugin works with a PCA9685 circuit connected to the Platypush host over I2C interface. This plugin works with a PCA9685 circuit connected to the Platypush host over I2C interface.
""" """
MIN_PWM_VALUE = 0 def __init__(self, frequency: float, min_duty_cycle: int = 0, max_duty_cycle: int = 0xffff, channels: Iterable[int] = tuple(range(16)), **kwargs):
MAX_PWM_VALUE = 0xffff
N_CHANNELS = 16
def __init__(self, frequency: float, step_value: float = 0.1, step_duration: float = 0.05, **kwargs):
""" """
:param frequency: Default PWM frequency to use for the driver, in Hz. :param frequency: Default PWM frequency to use for the driver, in Hz.
:param step_value: How much each channel value should be increased/decreased on each step. :param min_duty_cycle: Minimum PWM duty cycle (you can often find it in the documentation of your device).
:param step_duration: Length of each step transient when writing PWM values (default: 0.05 seconds). Default: 0.
:param max_duty_cycle: Maximum PWM duty cycle (you can often find it in the documentation of your device).
Default: 0xffff.
:param Indices of the default channels to be controlled (default: all channels,
i.e. ``[0-15]``).
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self.frequency = frequency self.frequency = frequency
self.step_value = step_value self.min_duty_cycle = min_duty_cycle
self.step_duration = step_duration self.max_duty_cycle = max_duty_cycle
self.channels = channels
self._pca = None self._pca = None
@staticmethod
def _convert_percent_to_duty_cycle(value: float) -> int:
"""
Convert a duty cycle percentage value to a PCA9685 value between 0 and ``0xffff``.
:param value: Duty cycle value, between 0 and 1.
:return: Duty cycle 16-bit value, between 0 and ``0xffff``.
"""
return int(value * 65535)
@staticmethod
def _convert_duty_cycle_to_percent(value: int) -> float:
"""
Convert a PCA9685 duty cycle value value to a percentage value.
:param value: Duty cycle 16-bit value, between 0 and ``0xffff``.
:return: Duty cycle percentage, between 0 and 1.
"""
return value / 65535
@action @action
def write(self, channels: Dict[int, float], frequency: Optional[float] = None, step_value: Optional[float] = None, def write(self, value: Optional[int] = None, channels: Optional[Dict[int, float]] = None,
frequency: Optional[float] = None, step: Optional[int] = None,
step_duration: Optional[float] = None): step_duration: Optional[float] = None):
""" """
Send PWM values to the specified channels. Send PWM values to the channels.
:param channels: Map of the values to be written, as ``channel_index -> value``, where value is a real number :param value: Send the value to all the channels (or to all the configured default channels).
between 0.0 (minimum duty cycle) and 1.0 (maximum duty cycle). ``value`` and ``channels`` are mutually exclusive.
:param channels: Map of the values to be written, as a ``channel_index -> value``, where value is a real number.
``value`` and ``channels`` are mutually exclusive.
:param frequency: Override default frequency. :param frequency: Override default frequency.
:param step_value: Override default step value. :param step: If set, then the PWM duty cycle will be increased/decreased by
:param step_duration: Override default step duration. this much per cycle (i.e. 1/frequency). This is useful when dealing with PWM
devices that require smooth transitions, arming sequences or ramping signals.
If None (default) then the new PWM values will be directly written with no
ramping logic.
:param step_duration: If step is configured, this parameter identifies how long
each step should last (default: ``1/frequency``).
""" """
import busio import busio
from board import SCL, SDA from board import SCL, SDA
from adafruit_pca9685 import PCA9685 from adafruit_pca9685 import PCA9685
values = {k: self._convert_percent_to_duty_cycle(v) for k, v in channels.items()} if value is not None:
step_value = self._convert_percent_to_duty_cycle(step_value if step_value is not None else self.step_value) assert self.channels, 'No default channels configured'
step_duration = step_duration if step_duration is not None else self.step_duration channels = {i: value for i in self.channels}
assert channels, 'Both value and channels are missing'
i2c_bus = busio.I2C(SCL, SDA) i2c_bus = busio.I2C(SCL, SDA)
pca = self._pca or PCA9685(i2c_bus) pca = self._pca = self._pca or PCA9685(i2c_bus)
pca.frequency = frequency or self.frequency pca.frequency = frequency or self.frequency
self._pca = pca step_duration = step_duration or 1/pca.frequency
if not step:
for i, val in channels.items():
pca.channels[i].duty_cycle = val
return
done = False done = False
cur_values = {
i: channel.duty_cycle
for i, channel in enumerate(pca.channels)
}
while not done: while not done:
done = True done = True
for channel, value in values.items(): for i, val in channels.items():
if abs(value - pca.channels[channel].duty_cycle) < 2: if val == cur_values[i]:
continue continue
done = False done = False
if value > pca.channels[channel].duty_cycle: val = min(cur_values[i] + step, val, self.max_duty_cycle) \
pca.channels[channel].duty_cycle = min(pca.channels[channel].duty_cycle + step_value, if val > pca.channels[i].duty_cycle \
value, else max(cur_values[i] - step, val, self.min_duty_cycle)
self.MAX_PWM_VALUE)
else: pca.channels[i].duty_cycle = cur_values[i] = val
pca.channels[channel].duty_cycle = max(pca.channels[channel].duty_cycle - step_value,
value,
self.MIN_PWM_VALUE)
time.sleep(step_duration) time.sleep(step_duration)
@ -119,10 +118,10 @@ class PwmPca9685Plugin(Plugin):
channel, as a percentage value between 0 and 1. channel, as a percentage value between 0 and 1.
""" """
if not self._pca: if not self._pca:
return {i: 0 for i in range(self.N_CHANNELS)} return {i: 0 for i in self.channels}
return { return {
i: self._convert_duty_cycle_to_percent(channel.duty_cycle) i: channel.duty_cycle
for i, channel in enumerate(self._pca.channels) for i, channel in enumerate(self._pca.channels)
} }
@ -148,3 +147,4 @@ class PwmPca9685Plugin(Plugin):
return return
self._pca.reset() self._pca.reset()