auto-trainer-core 2.0.1.dev349__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. auto_trainer_core-2.0.1.dev349.dist-info/METADATA +19 -0
  2. auto_trainer_core-2.0.1.dev349.dist-info/RECORD +73 -0
  3. auto_trainer_core-2.0.1.dev349.dist-info/WHEEL +5 -0
  4. auto_trainer_core-2.0.1.dev349.dist-info/top_level.txt +1 -0
  5. autotrainer/core/__init__.py +319 -0
  6. autotrainer/core/analysis/__init__.py +6 -0
  7. autotrainer/core/analysis/alarm_monitor.py +484 -0
  8. autotrainer/core/analysis/audio_spectrum_data.py +27 -0
  9. autotrainer/core/analysis/audio_spectrum_monitor.py +134 -0
  10. autotrainer/core/analysis/auto_tunnel_fan_monitor.py +92 -0
  11. autotrainer/core/analysis/detector.py +179 -0
  12. autotrainer/core/analysis/external_doors_monitor.py +124 -0
  13. autotrainer/core/analysis/global_animal_presence_monitor.py +69 -0
  14. autotrainer/core/analysis/head_fix_measurement.py +15 -0
  15. autotrainer/core/analysis/headbar_pressure_monitor.py +158 -0
  16. autotrainer/core/analysis/load_cell_monitor.py +377 -0
  17. autotrainer/core/analysis/load_cell_tare_monitor.py +217 -0
  18. autotrainer/core/analysis/measurement_data.py +80 -0
  19. autotrainer/core/analysis/pellet_position_monitor.py +108 -0
  20. autotrainer/core/analysis/sensor_analysis.py +390 -0
  21. autotrainer/core/analysis/system_fault_monitor.py +81 -0
  22. autotrainer/core/analysis/system_maintenance_monitor.py +88 -0
  23. autotrainer/core/animal/__init__.py +1 -0
  24. autotrainer/core/animal/animal_subject.py +208 -0
  25. autotrainer/core/capture.py +25 -0
  26. autotrainer/core/configuration/__init__.py +88 -0
  27. autotrainer/core/configuration/alarm_configuration.py +55 -0
  28. autotrainer/core/configuration/animal_presence_configuration.py +17 -0
  29. autotrainer/core/configuration/behavior_configuration.py +323 -0
  30. autotrainer/core/configuration/camera_configuration.py +108 -0
  31. autotrainer/core/configuration/external_doors_monitor_configuration.py +9 -0
  32. autotrainer/core/configuration/hardware_configuration.py +30 -0
  33. autotrainer/core/configuration/inference_configuration.py +26 -0
  34. autotrainer/core/configuration/load_cell_config.py +61 -0
  35. autotrainer/core/configuration/persistence_configuration.py +29 -0
  36. autotrainer/core/configuration/presence_detection_configuration.py +9 -0
  37. autotrainer/core/configuration/system_configuration.py +211 -0
  38. autotrainer/core/configuration/system_fault_config.py +9 -0
  39. autotrainer/core/configuration/system_maintenance_config.py +17 -0
  40. autotrainer/core/diamond_triangle_config.py +150 -0
  41. autotrainer/core/event/__init__.py +54 -0
  42. autotrainer/core/event/api_event_kind.py +5 -0
  43. autotrainer/core/event/api_event_plugin.py +57 -0
  44. autotrainer/core/event/event_info.py +53 -0
  45. autotrainer/core/event/event_manager.py +296 -0
  46. autotrainer/core/event/event_manager_plugin.py +45 -0
  47. autotrainer/core/event/file_event_plugin.py +91 -0
  48. autotrainer/core/event/logger_event_plugin.py +43 -0
  49. autotrainer/core/fixed_array_multiqueue.py +263 -0
  50. autotrainer/core/fixed_array_queue.py +124 -0
  51. autotrainer/core/frame_index.py +34 -0
  52. autotrainer/core/interfaces.py +347 -0
  53. autotrainer/core/logging.py +714 -0
  54. autotrainer/core/message/__init__.py +26 -0
  55. autotrainer/core/message/audio_spectrum_message.py +21 -0
  56. autotrainer/core/message/measurement_message.py +61 -0
  57. autotrainer/core/message/message_handler.py +158 -0
  58. autotrainer/core/message/motor_configuration_message.py +128 -0
  59. autotrainer/core/message/system_command_kind.py +74 -0
  60. autotrainer/core/message/system_message_handler.py +122 -0
  61. autotrainer/core/message/system_status_message.py +112 -0
  62. autotrainer/core/multiproc.py +50 -0
  63. autotrainer/core/notification/__init__.py +2 -0
  64. autotrainer/core/notification/notification_center.py +80 -0
  65. autotrainer/core/notification/trigger_notification.py +27 -0
  66. autotrainer/core/observable_object.py +105 -0
  67. autotrainer/core/perf_monitor.py +77 -0
  68. autotrainer/core/pose_elements.py +196 -0
  69. autotrainer/core/project/__init__.py +19 -0
  70. autotrainer/core/project/project_info.py +486 -0
  71. autotrainer/core/queue_util.py +60 -0
  72. autotrainer/core/reach_event.py +38 -0
  73. autotrainer/core/video_detection.py +326 -0
@@ -0,0 +1,26 @@
1
+ import dataclasses
2
+ from typing import Any, Tuple, Dict
3
+
4
+
5
+ @dataclasses.dataclass
6
+ class SystemDataArgsKwargs:
7
+ """A dedicated dataclass to pass data any desired args and/or kwargs as a single value for system commands"""
8
+
9
+ def __init__(self, *args, **kwargs):
10
+ super().__init__()
11
+ self.args = args
12
+ self.kwargs = kwargs
13
+
14
+ args: Tuple[Any] = ()
15
+ kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict)
16
+
17
+
18
+ from .audio_spectrum_message import AudioSpectrumMessage
19
+ from .measurement_message import MeasurementMessageProtocol
20
+ from .message_handler import MessageHandler
21
+ from .motor_configuration_message import MotorConfigurations, ServoConfigMessage, \
22
+ StepperConfigMessage, Motor
23
+ from .system_command_kind import SystemCommandKind
24
+ from .system_message_handler import SystemMessageHandler
25
+ from .system_status_message import SystemStatusMessageKind
26
+
@@ -0,0 +1,21 @@
1
+ from typing import Protocol, List
2
+
3
+
4
+ class AudioSpectrumMessage(Protocol):
5
+ @property
6
+ def when(self) -> float:
7
+ """Value of time.time() or equivalent absolute time provided by the hardware."""
8
+ pass
9
+
10
+ @property
11
+ def index(self) -> int:
12
+ """
13
+ A relative time index in nanoseconds. Provides finer resolution between measurements than absolute time may
14
+ provide on some platforms. By default, would be provided by time.perf_counter_ns().
15
+ """
16
+ pass
17
+
18
+ @property
19
+ def magnitudes(self) -> List[float]:
20
+ """The audio spectrum magnitudes in dB."""
21
+ pass
@@ -0,0 +1,61 @@
1
+ from typing import Protocol
2
+
3
+
4
+ class MeasurementMessageProtocol(Protocol):
5
+ """
6
+ MeasurementMessage defines the expected interface for measurement data provided by the hardware. This is expected
7
+ object passed as part of a SystemStatusMessageKind.MEASUREMENTS message.
8
+ """
9
+
10
+ @property
11
+ def when(self) -> float:
12
+ """Value of time.time() or equivalent absolute time provided by the hardware."""
13
+ pass
14
+
15
+ # Deprecated
16
+ @property
17
+ def timestamp(self) -> int:
18
+ """
19
+ A relative time index in nanoseconds. Provides finer resolution between measurements than absolute time may
20
+ provide on some platforms. By default, would be provided by time.perf_counter_ns().
21
+ """
22
+ pass
23
+
24
+ @property
25
+ def index(self) -> int:
26
+ """
27
+ A relative time index in nanoseconds. Provides finer resolution between measurements than absolute time may
28
+ provide on some platforms. By default, would be provided by time.perf_counter_ns().
29
+ """
30
+ pass
31
+
32
+ @property
33
+ def weight(self) -> float:
34
+ """The load cell value in grams."""
35
+ pass
36
+
37
+ @property
38
+ def pressure(self) -> float:
39
+ """The force detector value in the range of [0, 1024]"""
40
+ pass
41
+
42
+ # Deprecated
43
+ @property
44
+ def switch(self) -> float:
45
+ """The head contact DIO value as 0 or 1."""
46
+ pass
47
+
48
+ @property
49
+ def temperature(self) -> float:
50
+ """The temperature in Celsius."""
51
+ pass
52
+
53
+ @property
54
+ def humidity(self) -> float:
55
+ """The relative humidity (%)."""
56
+ pass
57
+
58
+ @property
59
+ def head_contact(self) -> bool:
60
+ """The head contact DIO value state. True if contact is made, False otherwise."""
61
+ pass
@@ -0,0 +1,158 @@
1
+ import functools
2
+ import logging
3
+ import threading
4
+ import time
5
+ from functools import partial
6
+ from queue import Queue, Empty
7
+ from threading import Thread
8
+ from typing import Callable, Union, Any, Optional
9
+
10
+ from autotrainer.core.logging import get_verbose_logger
11
+ from ..observable_object import ObservableObject
12
+
13
+ from .system_status_message import SystemStatusMessageKind
14
+
15
+ logger = get_verbose_logger(__name__)
16
+
17
+ TERMINATE = -1001
18
+
19
+
20
+ class MessageHandler(ObservableObject):
21
+ """
22
+ Base class for handling messages that come from the hardware. It has two primary purposes.
23
+
24
+ The first is to ensure that any middle-management of information or data happens in a separate thread from both the
25
+ hardware interfacing and any downstream consumers without requiring either the hardware code or the downstream code
26
+ to know whether that is taken care of or not - other than knowing to call start() on this object when ready to
27
+ receive messages.
28
+
29
+ The second is to insulate downstream consumers from any specifics of the hardware implementation such as how
30
+ commands are acknowledged, what capabilities different versions of the hardware may have, etc. Subclasses for
31
+ elements such as pellet delivery, tunnel behavior, and sensor measurements present properties (with change
32
+ notification), callbacks, and other means of making the data available in a consistent format that downstream
33
+ consumers care about rather than as specifically implemented the hardware.
34
+ """
35
+ FIRMWARE_VERSION_PROPERTY = "firmware_version"
36
+
37
+ HEAD_MAGNET_INTENSITY_PROPERTY = "head_magnet_intensity"
38
+ HEAD_GATE_PROPERTY = "gate_angle"
39
+
40
+ DEVICE_X_PROPERTY = "device_x"
41
+ DEVICE_Y_PROPERTY = "device_y"
42
+ DEVICE_Z_PROPERTY = "device_z"
43
+
44
+ STEPPER_X_PROPERTY = "stepper_x"
45
+ STEPPER_Y_PROPERTY = "stepper_y"
46
+ STEPPER_Z_PROPERTY = "stepper_z"
47
+
48
+ LOAD_ARM_ANGLE_PROPERTY = "load_angle"
49
+ COVER_ARM_ANGLE_PROPERTY = "cover_angle"
50
+ TUNNEL_FAN_PROPERTY = "tunnel_fan"
51
+
52
+ FRONT_DOOR_PROPERTY = "front_door"
53
+ DRAWER_DOOR_PROPERTY = "drawer_door"
54
+ SPARE_DOOR_PROPERTY = "spare_door"
55
+ EXT_BUTTON_PROPERTY = "ext_button"
56
+
57
+ STIMULI_PROPERTY = "stimuli"
58
+ CONFIG_PROPERTY = "config"
59
+
60
+ # type hints helper:
61
+ # dynamic event:
62
+ ack_received: Callable[[str], None]
63
+
64
+ def __init__(self, input_queue: Queue, name: str = "message-handler", event_names=()):
65
+ super().__init__(event_names=event_names + ("ack_received",))
66
+ self._input_queue = input_queue
67
+ self._name = name
68
+ self._current_thread = None
69
+
70
+ def __del__(self):
71
+ thread = self._current_thread
72
+ if thread is not None:
73
+ self.request_terminate()
74
+
75
+ @property
76
+ def input_queue(self) -> Queue:
77
+ return self._input_queue
78
+
79
+ def start(self):
80
+ if self._current_thread is None or not self._current_thread.is_alive():
81
+ logger.verbose("Starting system message handler thread")
82
+ self._current_thread = Thread(
83
+ target=self.run, name=self.__class__.__name__,
84
+ daemon=True, # in case main thread exits: also have the current handler thread to exit
85
+ )
86
+ self._current_thread.start()
87
+
88
+ def run(self):
89
+ try:
90
+ self._run()
91
+ except Exception as err:
92
+ logger.exception("Fatal error during run: %s", err)
93
+
94
+ def _run(self):
95
+ logger.debug(f"<{self._name}>: entering message event loop")
96
+ q_get = self._input_queue.get
97
+ task_done = self._input_queue.task_done
98
+ msg_received = self.message_received
99
+ tot_read_count = 0
100
+ t_next_check_size = time.perf_counter()
101
+ while True:
102
+ msg, data = q_get()
103
+ if __debug__:
104
+ tot_read_count += 1
105
+ t_now = time.perf_counter()
106
+ if t_now > t_next_check_size:
107
+ logger.debug("system message handler input queue: size=%s read=%.1f / s",
108
+ self._input_queue.qsize(), tot_read_count / 60)
109
+ t_next_check_size += 60
110
+ tot_read_count = 0
111
+ if msg == TERMINATE:
112
+ task_done()
113
+ break
114
+ elif msg == SystemStatusMessageKind.ACKNOWLEDGE:
115
+ try:
116
+ self.ack_received(data)
117
+ except Exception as err:
118
+ logger.exception("Error during ack_received callback: %s", err)
119
+ elif msg == SystemStatusMessageKind.FIRMWARE_VERSION:
120
+ self.property_changed(MessageHandler.FIRMWARE_VERSION_PROPERTY, data, None)
121
+ else:
122
+ try:
123
+ msg_received(msg, data)
124
+ except Exception as err:
125
+ logger.exception("Error during msg_received callback: msg=%s err=%s ; data=%s",
126
+ msg, err, data)
127
+ task_done()
128
+ logger.debug(f"<{self._name}>: exiting message event loop")
129
+
130
+ def request_terminate(self):
131
+ """
132
+ Sends a termination request to the device reader queue. The thread may have not yet terminated when this call
133
+ returns.
134
+ """
135
+ if self._input_queue is not None:
136
+ self._input_queue.put((TERMINATE, None))
137
+
138
+ def wait_terminated(self):
139
+ thread = self._current_thread
140
+ queue = self._input_queue
141
+ if thread is not None:
142
+ self._current_thread = None
143
+ thread.join(5)
144
+ if thread.is_alive():
145
+ logger.warning("message handler thread still alive")
146
+ if queue is not None:
147
+ self._input_queue = None
148
+ while not queue.empty():
149
+ try:
150
+ obj = queue.get_nowait()
151
+ except Empty:
152
+ break
153
+ logger.warning("drop unhandled %s ; %s", type(obj), obj)
154
+ queue.task_done()
155
+ queue.join()
156
+
157
+ def message_received(self, msg, _data):
158
+ pass
@@ -0,0 +1,128 @@
1
+ from typing import Protocol, Tuple
2
+ from enum import IntEnum
3
+
4
+
5
+ class Motor(IntEnum):
6
+ """
7
+ Identifiers for the different motor types
8
+ """
9
+
10
+ NONE = 0
11
+ TUNNEL_MAGNET_SERVO = 1
12
+ PELLET_X_MOTOR = 2
13
+ PELLET_Y_MOTOR = 3
14
+ PELLET_Z_MOTOR = 4
15
+ PELLET_COVER_SERVO = 5
16
+ PELLET_LOAD_SERVO = 6
17
+ TUNNEL_GATE_SERVO = 7
18
+ TUNNEL_FAN_SERVO = 8
19
+
20
+
21
+ class StepperConfigMessage(Protocol):
22
+ """
23
+ Protocol for the interface for obtaining stepper motor configuration entities
24
+ """
25
+
26
+ @property
27
+ def motor(self) -> Motor:
28
+ """Motor associated with the configuration"""
29
+
30
+
31
+ @property
32
+ def maximum_velocity(self) -> float:
33
+ """Units: turns/sec"""
34
+
35
+ @property
36
+ def maximum_acceleration(self) -> float:
37
+ """Units: turns/sec^2"""
38
+
39
+ @property
40
+ def minimum_step_inverted(self) -> int:
41
+ """Units: 1/min step"""
42
+
43
+ @property
44
+ def steps_per_revolution(self) -> float:
45
+ """Units: steps/rev"""
46
+
47
+ @property
48
+ def flip_limit_orientation(self) -> int:
49
+ """
50
+ 0 - Do no invert motor direction
51
+ 1 - Invert motor direction
52
+ """
53
+
54
+
55
+ class ServoConfigMessage(Protocol):
56
+ """
57
+ Protocol for the interface for obtaining servo motor configuration entities
58
+ """
59
+
60
+ @property
61
+ def motor(self) -> Motor:
62
+ """Motor associated with the configuration"""
63
+
64
+ @property
65
+ def maximum_velocity(self) -> float:
66
+ """Units: deg/sec"""
67
+
68
+ @property
69
+ def maximum_acceleration(self) -> float:
70
+ """Units: deg/sec^2"""
71
+
72
+ @property
73
+ def minimum_position(self) -> float:
74
+ """Units: deg"""
75
+
76
+ @property
77
+ def maximum_position(self) -> float:
78
+ """Units: deg"""
79
+
80
+ @property
81
+ def min_pwm_duration(self) -> float:
82
+ """Units: µs"""
83
+
84
+ @property
85
+ def max_pwm_duration(self) -> float:
86
+ """Units: µs"""
87
+
88
+ @property
89
+ def detach(self) -> bool:
90
+ """Is attach/detach used"""
91
+
92
+
93
+ class MotorConfigurations(Protocol):
94
+ """
95
+ Protocol for access to a full set of motor configurations
96
+ """
97
+
98
+ @property
99
+ def magnet_config(self) -> Tuple[Motor, ServoConfigMessage]:
100
+ """The magnet config"""
101
+
102
+ @property
103
+ def gate_config(self) -> Tuple[Motor, ServoConfigMessage]:
104
+ """The gate config"""
105
+
106
+ @property
107
+ def load_config(self) -> Tuple[Motor, ServoConfigMessage]:
108
+ """The pellet-load config"""
109
+
110
+ @property
111
+ def cover_config(self) -> Tuple[Motor, ServoConfigMessage]:
112
+ """The pellet-cover config"""
113
+
114
+ @property
115
+ def x_config(self) -> Tuple[Motor, StepperConfigMessage]:
116
+ """The pellet stepper X motor & config"""
117
+
118
+ @property
119
+ def y_config(self) -> Tuple[Motor, StepperConfigMessage]:
120
+ """The pellet stepper Y motor & config"""
121
+
122
+ @property
123
+ def z_config(self) -> Tuple[Motor, StepperConfigMessage]:
124
+ """The pellet stepper Z motor & config"""
125
+
126
+ @property
127
+ def tunnel_fan_config(self) -> Tuple[Motor, ServoConfigMessage]:
128
+ """The tunnel-fan motor & config"""
@@ -0,0 +1,74 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class SystemCommandKind(IntEnum):
5
+ """
6
+ This class defines the commands supported by the device. If there is a response beyond acknowledgement, it is
7
+ defined in SystemStatusMessageKind.
8
+
9
+ Commands are sent to the device with optional data and context information. The data is specific to the command
10
+ and may not be applicable to all commands. The context information is provided by the caller and expected back
11
+ as part of the acknowledgement that the command is complete. Note that complete may mean that the action is fully
12
+ executed (e.g., a move has reached its final position), not simply that the command was sent to the device.
13
+
14
+ The above is currently implemented and represented by the send_message() method in the DeviceConnection class.
15
+ """
16
+
17
+ # System commands unrelated to actual behavior.
18
+ REQUEST_VERSION = 1
19
+
20
+ # Actions nominally considered part of the tunnel/head fixation unit (magnet module in some nomenclature).
21
+ MOVE_MAGNET_SERVO = 101
22
+ UPDATE_SCALE_TARE = 102
23
+ OPEN_TUNNEL_GATE = 103
24
+ CLOSE_TUNNEL_GATE = 104
25
+
26
+ # Actions nominally considered part of the pellet delivery unit.
27
+ READ_MOTOR_CONFIGURATION = 201
28
+ WRITE_MOTOR_CONFIGURATION = 202
29
+ SET_LOAD_PELLET_PROCEDURE = 203
30
+ SET_SEND_PELLET_PROCEDURE = 204
31
+ SET_X = 205 # sets the X position for use by SEND_FIXED_XYZ
32
+ SET_Y = 206 # sets the Y position for use by SEND_FIXED_XYZ
33
+ SET_Z = 207 # sets the Z position for use by SEND_FIXED_XYZ
34
+ MOVE_LOAD_SERVO = 208
35
+ MOVE_COVER_SERVO = 209
36
+ SEND_HOME = 210
37
+ LOAD_PELLET = 211
38
+ SEND_PELLET = 212
39
+ RELEASE_PELLET = 213
40
+ COVER_PELLET = 214
41
+ SEND_TO_LIMITS = 215
42
+ SET_COVER_PELLET_PROCEDURE = 216
43
+ SET_RELEASE_PELLET_PROCEDURE = 217
44
+ DELAY = 218
45
+ SEND_FIXED_XYZ = 219
46
+ MOVE_X = 220 # moves X, only
47
+ MOVE_Y = 221 # moves Y, only
48
+ MOVE_Z = 222 # moves Z, only
49
+ MOVE_GATE_SERVO = 223
50
+
51
+ SEND_RETRACT = 224 # make y - 15, or 0 if lower.
52
+
53
+ SET_MOTOR_DRIFT = 225
54
+ SET_AUTO_CORRECT_DRIFT = 226
55
+
56
+ SERVO_ATTACH = 227
57
+ SERVO_DETACH = 228
58
+
59
+ TUNNEL_FAN_ON = 229
60
+ TUNNEL_FAN_OFF = 230
61
+ TUNNEL_FAN_SET = 231
62
+
63
+ # General actions.
64
+ PLAY_TONE = 301
65
+ SET_RGB_LED = 302
66
+ SET_DIGITAL_OUTPUT = 303
67
+ SET_ANALOG_OUTPUT = 304
68
+
69
+ # Not deprecated so long as the original hardware is supported, but a bit esoteric. It is not necessary for
70
+ # new hardware to support them or for new application/scripts to support them.
71
+ RAW_COMMAND = 1001 # TODO only used at command line for original hardware. Remove eventually.
72
+ SETTINGS = 1002 # TODO Specific to original hardware head fix unit and not used in user apps. Remove eventually.
73
+ STREAM_START = 1003 # TODO n/a new hardware, but required for original.
74
+ STREAM_STOP = 1004 # TODO n/a new hardware, but required for original.
@@ -0,0 +1,122 @@
1
+ import functools
2
+ import logging
3
+ import threading
4
+ from queue import Queue
5
+ from typing import Callable, List, Tuple, Optional
6
+
7
+ from ..analysis.sensor_analysis import SensorAnalysis
8
+
9
+ from .message_handler import MessageHandler
10
+ from .system_status_message import SystemStatusMessageKind
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SystemMessageHandler(MessageHandler):
16
+
17
+ def __init__(self, input_queue: Queue, *, sensor_analysis: Optional[SensorAnalysis] = None):
18
+ super().__init__(input_queue, name="system-message-handler")
19
+
20
+ self._measurement_callback = None
21
+ self._audio_callback = None
22
+
23
+ self._analysis = SensorAnalysis() if sensor_analysis is None else sensor_analysis
24
+
25
+ @property
26
+ def measurement_callback(self):
27
+ return self._measurement_callback
28
+
29
+ @measurement_callback.setter
30
+ def measurement_callback(self, measurement_callback: Callable[[Tuple], None]) -> None:
31
+ self._measurement_callback = measurement_callback
32
+
33
+ @property
34
+ def audio_callback(self):
35
+ return self._audio_callback
36
+
37
+ @audio_callback.setter
38
+ def audio_callback(self, audio_callback: Callable[[List[float]], None]) -> None:
39
+ prev = self._audio_callback
40
+ if prev is not None:
41
+ logger.info("Replacing audio callback %s with %s", prev, audio_callback)
42
+ self._audio_callback = audio_callback
43
+
44
+ @property
45
+ def analysis(self):
46
+ return self._analysis
47
+
48
+ def message_received(self, msg, data):
49
+ # TODO: These are treated as if the property has changed. If the number of event listeners increases or their
50
+ # behaviors are complex and do not check for change themselves, this could become a bottleneck. This could be
51
+ # updated to store previous values and only notify listeners on change, like a typical ObservableObject
52
+ # implementation. Keeping things simple for the time being.
53
+ if msg == SystemStatusMessageKind.MEASUREMENT or msg == SystemStatusMessageKind.MEASUREMENTS:
54
+ measures = self._analysis.measurements_received(data)
55
+ if self._measurement_callback is not None and len(measures) > 0:
56
+ self._measurement_callback(measures)
57
+
58
+ elif msg == SystemStatusMessageKind.AUDIO_SPECTRUM:
59
+ self._analysis.audio_spectrum_received(data)
60
+ if self._audio_callback is not None:
61
+ self._audio_callback(data.magnitudes)
62
+
63
+ elif msg == SystemStatusMessageKind.PELLET_X:
64
+ self.property_changed(MessageHandler.DEVICE_X_PROPERTY, data, None)
65
+
66
+ elif msg == SystemStatusMessageKind.PELLET_Y:
67
+ self.property_changed(MessageHandler.DEVICE_Y_PROPERTY, data, None)
68
+
69
+ elif msg == SystemStatusMessageKind.PELLET_Z:
70
+ self.property_changed(MessageHandler.DEVICE_Z_PROPERTY, data, None)
71
+
72
+ elif msg == SystemStatusMessageKind.PELLET_MOTOR_X:
73
+ self.property_changed(MessageHandler.DEVICE_X_PROPERTY, data.position, None)
74
+ self.property_changed(MessageHandler.STEPPER_X_PROPERTY, data, None)
75
+
76
+ elif msg == SystemStatusMessageKind.PELLET_MOTOR_Y:
77
+ self.property_changed(MessageHandler.DEVICE_Y_PROPERTY, data.position, None)
78
+ self.property_changed(MessageHandler.STEPPER_Y_PROPERTY, data, None)
79
+
80
+ elif msg == SystemStatusMessageKind.PELLET_MOTOR_Z:
81
+ self.property_changed(MessageHandler.DEVICE_Z_PROPERTY, data.position, None)
82
+ self.property_changed(MessageHandler.STEPPER_Z_PROPERTY, data, None)
83
+
84
+ elif msg == SystemStatusMessageKind.PELLET_LOAD:
85
+ self.property_changed(MessageHandler.LOAD_ARM_ANGLE_PROPERTY, data, None)
86
+
87
+ elif msg == SystemStatusMessageKind.PELLET_COVER:
88
+ self.property_changed(MessageHandler.COVER_ARM_ANGLE_PROPERTY, data, None)
89
+
90
+ elif msg == SystemStatusMessageKind.HEAD_MAGNET:
91
+ self.property_changed(MessageHandler.HEAD_MAGNET_INTENSITY_PROPERTY, data, None)
92
+
93
+ elif msg == SystemStatusMessageKind.TUNNEL_GATE_SERVO:
94
+ self.property_changed(MessageHandler.HEAD_GATE_PROPERTY, data, None)
95
+
96
+ elif msg == SystemStatusMessageKind.TUNNEL_FAN:
97
+ self.property_changed(MessageHandler.TUNNEL_FAN_PROPERTY, data, None)
98
+
99
+ elif msg == SystemStatusMessageKind.FRONT_DOOR:
100
+ # directly update related monitor, hopefully is very fast
101
+ self._analysis.external_doors_monitor.update_door_state(msg, data)
102
+ self.property_changed(MessageHandler.FRONT_DOOR_PROPERTY, data, None)
103
+
104
+ elif msg == SystemStatusMessageKind.DRAWER_DOOR:
105
+ # directly update related monitor, hopefully is very fast
106
+ self._analysis.external_doors_monitor.update_door_state(msg, data)
107
+ self.property_changed(MessageHandler.DRAWER_DOOR_PROPERTY, data, None)
108
+
109
+ elif msg == SystemStatusMessageKind.SPARE_DOOR:
110
+ self.property_changed(MessageHandler.SPARE_DOOR_PROPERTY, data, None)
111
+
112
+ elif msg == SystemStatusMessageKind.EXT_BUTTON:
113
+ self.property_changed(MessageHandler.EXT_BUTTON_PROPERTY, data, None)
114
+
115
+ elif msg == SystemStatusMessageKind.STIMULUS_INPUTS:
116
+ self.property_changed(MessageHandler.STIMULI_PROPERTY, data, None)
117
+
118
+ elif msg == SystemStatusMessageKind.MOTOR_CONFIGURATION:
119
+ self._on_property_changed("config", data, None)
120
+
121
+ else:
122
+ logger.warning("unhandled msg %s", msg)