Skip to content

Commit cc64971

Browse files
committed
magicbot: Allow struct tunables
1 parent 31dc5dd commit cc64971

File tree

1 file changed

+37
-12
lines changed

1 file changed

+37
-12
lines changed

Diff for: magicbot/magic_tunable.py

+37-12
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@
33
import inspect
44
import typing
55
import warnings
6-
from typing import Callable, Generic, Optional, TypeVar, overload
6+
from typing import Callable, Generic, Optional, Sequence, TypeVar, Union, overload
77

88
import ntcore
9-
from ntcore import NetworkTableInstance, Value
9+
from ntcore import NetworkTableInstance
1010
from ntcore.types import ValueT
1111

12+
13+
class StructSerializable(typing.Protocol):
14+
"""Any type that is a wpiutil.wpistruct."""
15+
16+
WPIStruct: typing.ClassVar
17+
18+
1219
T = TypeVar("T")
13-
V = TypeVar("V", bound=ValueT)
20+
V = TypeVar("V", bound=Union[ValueT, StructSerializable, Sequence[StructSerializable]])
1421

1522

1623
class tunable(Generic[V]):
@@ -69,7 +76,7 @@ def execute(self):
6976
"_ntsubtable",
7077
"_ntwritedefault",
7178
# "__doc__",
72-
"_mkv",
79+
"_topic_type",
7380
"_nt",
7481
)
7582

@@ -87,10 +94,15 @@ def __init__(
8794
self._ntdefault = default
8895
self._ntsubtable = subtable
8996
self._ntwritedefault = writeDefault
90-
d = Value.makeValue(default)
91-
self._mkv = Value.getFactoryByType(d.type())
9297
# self.__doc__ = doc
9398

99+
self._topic_type = _get_topic_type_for_value(self._ntdefault)
100+
if self._topic_type is None:
101+
checked_type: type = type(self._ntdefault)
102+
raise TypeError(
103+
f"tunable is not publishable to NetworkTables, type: {checked_type.__name__}"
104+
)
105+
94106
@overload
95107
def __get__(self, instance: None, owner=None) -> "tunable[V]": ...
96108

@@ -99,11 +111,23 @@ def __get__(self, instance, owner=None) -> V: ...
99111

100112
def __get__(self, instance, owner=None):
101113
if instance is not None:
102-
return instance._tunables[self].value
114+
return instance._tunables[self].get()
103115
return self
104116

105117
def __set__(self, instance, value: V) -> None:
106-
instance._tunables[self].setValue(self._mkv(value))
118+
instance._tunables[self].set(value)
119+
120+
121+
def _get_topic_type_for_value(value) -> Optional[Callable[[ntcore.Topic], typing.Any]]:
122+
topic_type = _get_topic_type(type(value))
123+
# bytes and str are Sequences. They must be checked before Sequence.
124+
if topic_type is None and isinstance(value, collections.abc.Sequence):
125+
if not value:
126+
raise ValueError(
127+
f"tunable default cannot be an empty sequence, got {value}"
128+
)
129+
topic_type = _get_topic_type(Sequence[type(value[0])]) # type: ignore [misc]
130+
return topic_type
107131

108132

109133
def setup_tunables(component, cname: str, prefix: Optional[str] = "components") -> None:
@@ -127,7 +151,7 @@ def setup_tunables(component, cname: str, prefix: Optional[str] = "components")
127151

128152
NetworkTables = NetworkTableInstance.getDefault()
129153

130-
tunables = {}
154+
tunables: dict[tunable, ntcore.Topic] = {}
131155

132156
for n in dir(cls):
133157
if n.startswith("_"):
@@ -142,11 +166,12 @@ def setup_tunables(component, cname: str, prefix: Optional[str] = "components")
142166
else:
143167
key = "%s/%s" % (prefix, n)
144168

145-
ntvalue = NetworkTables.getEntry(key)
169+
topic = prop._topic_type(NetworkTables.getTopic(key))
170+
ntvalue = topic.getEntry(prop._ntdefault)
146171
if prop._ntwritedefault:
147-
ntvalue.setValue(prop._ntdefault)
172+
ntvalue.set(prop._ntdefault)
148173
else:
149-
ntvalue.setDefaultValue(prop._ntdefault)
174+
ntvalue.setDefault(prop._ntdefault)
150175
tunables[prop] = ntvalue
151176

152177
component._tunables = tunables

0 commit comments

Comments
 (0)