11import functools
22import inspect
33import warnings
4- from typing import Generic , Optional , TypeVar
4+ from typing import Callable , Generic , Optional , TypeVar
55
66from networktables import NetworkTables
77from ntcore .value import Value
@@ -67,6 +67,7 @@ def execute(self):
6767 # "__doc__",
6868 "_mkv" ,
6969 "_nt" ,
70+ "_update_cb" ,
7071 )
7172
7273 def __init__ (
@@ -87,16 +88,35 @@ def __init__(
8788
8889 self ._mkv = Value .getFactory (default )
8990 self ._nt = NetworkTables
91+ self ._update_cb = None
9092
9193 def __get__ (self , instance , owner ) -> V :
9294 if instance is not None :
9395 return instance ._tunables [self ].value
9496 return self
9597
96- def __set__ (self , instance , value ) -> None :
98+ def __set__ (self , instance , value : V ) -> None :
9799 v = instance ._tunables [self ]
98100 self ._nt ._api .setEntryValueById (v ._local_id , self ._mkv (value ))
99101
102+ def set_callback (self , callback : Callable [[V ], None ]) -> None :
103+ """
104+ Set a method to be called when the tunable is updated over NetworkTables.
105+
106+ This can be useful, for example, for changing PID gains on a
107+ motor controller on the fly::
108+
109+ class Component:
110+ pid: ...
111+
112+ kP = tunable(0.01)
113+
114+ @kP.set_callback
115+ def set_kP(self, value: float) -> None:
116+ self.pid.setP(value)
117+ """
118+ self ._update_cb = lambda inst , notif : callback (inst , notif .value .value )
119+
100120
101121def setup_tunables (component , cname : str , prefix : Optional [str ] = "components" ) -> None :
102122 """
@@ -137,6 +157,13 @@ def setup_tunables(component, cname: str, prefix: Optional[str] = "components")
137157 )
138158 tunables [prop ] = ntvalue
139159
160+ if prop ._update_cb :
161+ prop ._nt ._api .addEntryListenerById (
162+ ntvalue ._local_id ,
163+ functools .partial (prop ._update_cb , component ),
164+ NetworkTables .NotifyFlags .UPDATE ,
165+ )
166+
140167 component ._tunables = tunables
141168
142169
0 commit comments