Refactored PCA9685 backend
This commit is contained in:
parent
fc1d9ad3e6
commit
a39452124d
2 changed files with 59 additions and 56 deletions
|
@ -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))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue