Skip to content

Commit

Permalink
A bunch of random stuff...
Browse files Browse the repository at this point in the history
- Run black
- Bump mypy version
- Drop 3.7 and add 3.12 to CI test runs
- Fix some 3.7 specific imports.
  • Loading branch information
clint-lawrence committed Jun 4, 2024
1 parent 5ddeab4 commit 8b94e31
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 1 addition & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ package_dir =
packages = find:
include_package_data = True
zip_safe = False
python_requires = ~=3.7
python_requires = ~=3.8

install_requires =
pyvisa
Expand All @@ -35,8 +35,6 @@ install_requires =
numpy
PyDAQmx
# for typing.protocol
typing_extensions ; python_version < "3.8"
importlib-metadata >= 1.0 ; python_version < "3.8"
platformdirs

[options.packages.find]
Expand Down
93 changes: 61 additions & 32 deletions src/fixate/core/switching.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
Collection,
Dict,
Any,
FrozenSet, Set
FrozenSet,
Set,
)
from dataclasses import dataclass
from functools import reduce
Expand Down Expand Up @@ -84,7 +85,9 @@ def __or__(self, other: PinUpdate) -> PinUpdate:
return PinUpdate(
setup=self.setup | other.setup,
final=self.final | other.final,
minimum_change_time=max(self.minimum_change_time, other.minimum_change_time)
minimum_change_time=max(
self.minimum_change_time, other.minimum_change_time
),
)
return NotImplemented

Expand Down Expand Up @@ -155,7 +158,9 @@ def multiplex(self, signal_output: Signal, trigger_update: bool = True) -> None:
"""
if signal_output not in self._signal_map:
name = self.__class__.__name__
raise ValueError(f"Signal '{signal_output}' not valid for multiplexer '{name}'")
raise ValueError(
f"Signal '{signal_output}' not valid for multiplexer '{name}'"
)

setup, final = self._calculate_pins(self._state, signal_output)
self._update_pins(PinUpdate(setup, final, self.clearing_time), trigger_update)
Expand All @@ -177,13 +182,15 @@ def switch_through_all_signals(self) -> Generator[str, None, None]:
self.multiplex(signal)
yield f"{self.__class__.__name__}: {signal}"

def reset(self, trigger_update: bool=True) -> None:
def reset(self, trigger_update: bool = True) -> None:
self.multiplex("", trigger_update)

###########################################################################
# The following methods are potential candidates to override in a subclass

def _calculate_pins(self, old_signal: Signal, new_signal: Signal) -> tuple[PinSetState, PinSetState]:
def _calculate_pins(
self, old_signal: Signal, new_signal: Signal
) -> tuple[PinSetState, PinSetState]:
"""
Calculate the pin sets for the two-step state change.
Expand Down Expand Up @@ -226,7 +233,9 @@ def _map_signals(self) -> SignalMap:
elif hasattr(self, "map_list"):
return {sig: frozenset(pins) for sig, *pins in self.map_list}
else:
raise ValueError("VirtualMux subclass must define either map_tree or map_list")
raise ValueError(
"VirtualMux subclass must define either map_tree or map_list"
)

def _map_tree(self, tree: TreeDef, pins: PinList, fixed_pins: PinSet) -> SignalMap:
"""recursively add nested signal lists to the signal map.
Expand Down Expand Up @@ -393,17 +402,21 @@ class Mux(VirtualMux):
bits_at_this_level = (len(tree) - 1).bit_length()
pins_at_this_level = pins[:bits_at_this_level]

for signal_or_tree, pins_for_signal in zip(tree, generate_bit_sets(pins_at_this_level)):
for signal_or_tree, pins_for_signal in zip(
tree, generate_bit_sets(pins_at_this_level)
):
if signal_or_tree is None:
continue
if isinstance(signal_or_tree, Signal):
signal_map[signal_or_tree] = frozenset(pins_for_signal) | fixed_pins
else:
signal_map.update(self._map_tree(
tree=signal_or_tree,
pins=pins[bits_at_this_level:],
fixed_pins=frozenset(pins_for_signal) | fixed_pins,
))
signal_map.update(
self._map_tree(
tree=signal_or_tree,
pins=pins[bits_at_this_level:],
fixed_pins=frozenset(pins_for_signal) | fixed_pins,
)
)

return signal_map

Expand All @@ -412,8 +425,7 @@ def __repr__(self) -> str:

@staticmethod
def _default_update_pins(
pin_updates: PinUpdate,
trigger_update: bool = True
pin_updates: PinUpdate, trigger_update: bool = True
) -> None:
"""
Output callback to effect a state change in the mux.
Expand Down Expand Up @@ -442,7 +454,9 @@ class VirtualSwitch(VirtualMux):
pin_name: Pin = ""
map_tree = ("FALSE", "TRUE")

def multiplex(self, signal_output: Union[Signal, bool], trigger_update: bool = True) -> None:
def multiplex(
self, signal_output: Union[Signal, bool], trigger_update: bool = True
) -> None:
if signal_output is True:
signal = "TRUE"
elif signal_output is False:
Expand All @@ -463,7 +477,9 @@ def __init__(
class RelayMatrixMux(VirtualMux):
clearing_time = 0.01

def _calculate_pins(self, old_signal: Signal, new_signal: Signal) -> tuple[PinSetState, PinSetState]:
def _calculate_pins(
self, old_signal: Signal, new_signal: Signal
) -> tuple[PinSetState, PinSetState]:
"""
Override of _calculate_pins to implement break-before-make switching.
"""
Expand Down Expand Up @@ -503,7 +519,9 @@ class PinValueAddressHandler(AddressHandler):

def __init__(self) -> None:
super().__init__()
self._pin_lookup = {pin: bit for pin, bit in zip(self.pin_list, bit_generator())}
self._pin_lookup = {
pin: bit for pin, bit in zip(self.pin_list, bit_generator())
}

def set_pins(self, pins: Collection[Pin]) -> None:
value = sum(self._pin_lookup[pin] for pin in pins)
Expand All @@ -524,6 +542,7 @@ class FTDIAddressHandler(PinValueAddressHandler):
often. FT232 is used to bit-bang to shift register that are control
the switching in a jig.
"""

def _update_output(self, value: int) -> None:
raise NotImplementedError

Expand All @@ -532,23 +551,22 @@ class VirtualAddressMap:
"""
The supervisor loops through the attached virtual multiplexers each time a mux update is triggered.
"""

def __init__(self, handlers: Sequence[AddressHandler]):
# used to work out which pins get routed to which address handler
self._handler_pin_sets: list[tuple[PinSet, AddressHandler]] = []
for handler in handlers:
self._handler_pin_sets.append((frozenset(handler.pin_list), handler))
self._all_pins = frozenset(itertools.chain.from_iterable(handler.pin_list for handler in handlers))
self._all_pins = frozenset(
itertools.chain.from_iterable(handler.pin_list for handler in handlers)
)

# a list of updates that haven't been sent to address handlers yet. This
# allows a few mux changes to get updated at the same time.
self._pending_updates: list[PinUpdate] = []
self._active_pins: set[Pin] = set()

def add_update(
self,
pin_update: PinUpdate,
trigger_update: bool = True
) -> None:
def add_update(self, pin_update: PinUpdate, trigger_update: bool = True) -> None:
"""This method should be registered with each virtual mux to route pin changes."""
self._pending_updates.append(pin_update)

Expand Down Expand Up @@ -601,7 +619,6 @@ def reset(self) -> None:
"""
self._dispatch_pin_state(PinSetState(off=self._all_pins))


def update_input(self) -> None:
"""
Iterates through the address_handlers and reads the values back to update the pin values for the digital inputs
Expand All @@ -610,11 +627,15 @@ def update_input(self) -> None:
raise NotImplementedError

# used in a few scripts
def update_pin_by_name(self, name: Pin, value: bool, trigger_update: bool =True) -> None:
def update_pin_by_name(
self, name: Pin, value: bool, trigger_update: bool = True
) -> None:
raise NotImplementedError

# not used in any scripts
def update_pins_by_name(self, pins: Collection[Pin], trigger_update: bool=True) -> None:
def update_pins_by_name(
self, pins: Collection[Pin], trigger_update: bool = True
) -> None:
raise NotImplementedError

def __getitem__(self, item: Pin) -> bool:
Expand All @@ -638,6 +659,7 @@ class JigMuxGroup(MuxGroup):
mux_one: MuxOne = field(default_factory=MuxOne)
mux_two: MuxTwo = field(default_factory=MuxTwo)
"""

def get_multiplexers(self) -> list[VirtualMux]:
return [attr for attr in self.__dict__.values() if isinstance(attr, VirtualMux)]

Expand All @@ -663,14 +685,19 @@ class JigDriver(Generic[JigSpecificMuxGroup]):
The jig driver joins muxes to handlers by matching up pin definitions.
"""
def __init__(self, mux_group_factory: Callable[[],JigSpecificMuxGroup], handlers: Sequence[AddressHandler]):

def __init__(
self,
mux_group_factory: Callable[[], JigSpecificMuxGroup],
handlers: Sequence[AddressHandler],
):
self.virtual_map = VirtualAddressMap(handlers)

self.mux = mux_group_factory()
for mux in self.mux.get_multiplexers():
# Perhaps we should instantiate the virtual mux here
# and pass in the virtual_map.add_update. But we'd have to do some
# magic in the MuxGroup call to pass add_update to each VirtualMux
# and pass in the virtual_map.add_update. But we'd have to do some
# magic in the MuxGroup call to pass add_update to each VirtualMux
# constructor, and I was hoping to just use a dataclass...
mux._update_pins = self.virtual_map.add_update

Expand Down Expand Up @@ -710,7 +737,9 @@ def generate_bit_sets(bits: Sequence[T]) -> Generator[set[T], None, None]:
list(generate_bit_set(["x0", "x1"])) -> [set(), {'x0'}, {'x1'}, {'x0', 'x1'}]
"""
int_list = range(1 << len(bits)) if len(bits) != 0 else range(0)
return ({bit for i, bit in enumerate(bits) if (1 << i) & index} for index in int_list)
return (
{bit for i, bit in enumerate(bits) if (1 << i) & index} for index in int_list
)


def bit_generator() -> Generator[int, None, None]:
Expand Down
6 changes: 1 addition & 5 deletions src/fixate/drivers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
try:
from typing import Protocol
except ImportError:
# Protocol added in python 3.8
from typing_extensions import Protocol
from typing import Protocol

import pubsub.pub

Expand Down
24 changes: 18 additions & 6 deletions test/core/test_switching.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from fixate.core.switching import generate_bit_sets, VirtualMux, bit_generator, PinSetState, PinUpdate
from fixate.core.switching import (
generate_bit_sets,
VirtualMux,
bit_generator,
PinSetState,
PinUpdate,
)


################################################################
Expand All @@ -22,10 +28,11 @@ def test_generate_bit_sets_multiple_bits():
{"b2"},
{"b2", "b0"},
{"b2", "b1"},
{"b2", "b1", "b0"}
{"b2", "b1", "b0"},
]
assert list(generate_bit_sets(["b0", "b1", "b2"])) == expected


def test_bit_generator():
"""b1, b10, b100, b1000, ..."""
bit_gen = bit_generator()
Expand All @@ -34,6 +41,7 @@ def test_bit_generator():
expected = [1, 2, 4, 8, 16, 32, 64, 128]
assert actual == expected


################################################################
# virtual mux definitions

Expand Down Expand Up @@ -107,27 +115,31 @@ class NestedVirtualMux(VirtualMux):
"a2_b1_c1": {"x1", "x2", "x4"},
}


################################################################
# Helper dataclasses


def test_pin_set_state_or():
a = PinSetState(frozenset("ab"), frozenset("xy"))
b = PinSetState(frozenset("cd"), frozenset("x"))
assert a | b == PinSetState(frozenset("abcd"), frozenset("xy"))


def test_pin_update_or():
a = PinUpdate(
PinSetState(frozenset("a"), frozenset("b")),
PinSetState(frozenset(), frozenset("yz")),
1.0)
1.0,
)
b = PinUpdate(
PinSetState(frozenset("x"), frozenset()),
PinSetState(frozenset("c"), frozenset("d")),
2.0
2.0,
)
expected = PinUpdate(
PinSetState(frozenset("ax"), frozenset("b")),
PinSetState(frozenset("c"), frozenset("yzd")),
2.0
2.0,
)
assert expected == a | b
assert expected == a | b
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py37,py38,py39,py310,py311,black,mypy
envlist = py38,py39,py310,py311,py212,black,mypy
isolated_build = True

[testenv]
Expand Down Expand Up @@ -38,7 +38,7 @@ markers =

[testenv:mypy]
basepython = python3
deps = mypy==1.3
deps = mypy==1.10.0
# mypy gives different results if you actually install the stuff before you check it
# separate cache to stop weirdness around sharing cache with other instances of mypy
commands = mypy --cache-dir="{envdir}/mypy_cache" --config-file=mypy.ini

0 comments on commit 8b94e31

Please sign in to comment.