Source code for pyniryo_nate.components.firmware
import threading
from typing import Callable
from .base_api_component import BaseAPIComponent
from .._internal import paths_gen, transport_models, topics_gen
from ..models import FirmwareState, PlcSafetyState, Unsubscribe
FirmwareStateCallback = Callable[[FirmwareState], None]
PlcSafetyStateCallback = Callable[[PlcSafetyState], None]
[docs]
class Firmware(BaseAPIComponent):
"""
Firmware component for accessing the state of the robot firmware and the PLC safety
chain, and for controlling the firmware container.
"""
[docs]
def get_state(self) -> FirmwareState:
"""
Get the current state of the robot firmware.
:return: The current firmware state.
"""
resp = self._http_client.get(paths_gen.Firmware.GET_FIRMWARE_STATE, transport_models.s.FirmwareState)
return FirmwareState.from_transport_model(resp)
[docs]
def get_error_report(self) -> str:
"""
Get the current firmware error report.
:return: A human-readable message describing the current firmware error, if any.
"""
resp = self._http_client.get(paths_gen.Firmware.GET_ERROR_REPORT, transport_models.s.ErrorReport)
return resp.message
[docs]
def clear_errors(self) -> None:
"""
Clear the current firmware errors.
"""
self._http_client.delete(paths_gen.Firmware.CLEAR_FIRMWARE_ERRORS, transport_models.EmptyPayload)
[docs]
def get_plc_safety_state(self) -> PlcSafetyState:
"""
Get the current state of the PLC safety chain.
:return: The current PLC safety state.
"""
resp = self._http_client.get(paths_gen.Firmware.GET_PLC_SAFETY_STATE, transport_models.s.PlcSafetyState)
return PlcSafetyState.from_transport_model(resp)
[docs]
def set_plc_safety_state(self, state: PlcSafetyState) -> None:
"""
Set the state of the PLC safety chain. Only available when safety is simulated: on a
real robot the PLC state is owned by the wired safety chain and cannot be set through
the API.
:param state: The PLC safety state to set.
"""
self._http_client.patch(paths_gen.Firmware.SET_PLC_SAFETY_STATE, transport_models.EmptyPayload,
state.to_transport_model())
[docs]
def enable(self) -> None:
"""
Request the arming of the robot through the safety controller, the same path as the physical initialization
button. Available while disarmed.
Acceptance is not arming: observe ``safety.armed`` with :meth:`on_state` or :meth:`get_state`, or call
:meth:`wait_armed`. The robot bounds the request to two seconds. Do not retry after a timeout, the impulse
may already have been transmitted; this client retries connection failures only, never a response.
:raises ClientError: If the request is refused, an impulse already pending included.
:raises ServerError: If the safety controller cannot be reached (503), did not answer in time (504), or
failed.
"""
self._http_client.post(paths_gen.Firmware.FIRMWARE_ENABLE, transport_models.EmptyPayload,
transport_models.EmptyPayload())
[docs]
def wait_armed(self, timeout: float = -1) -> None:
"""
Block until the safety module reports the robot armed. The firmware state is retained, so a robot already
armed returns at once.
:param timeout: Maximum time to wait, in seconds. Zero or a negative value waits indefinitely.
:raises TimeoutError: If the timeout expires before the robot is armed.
"""
armed = threading.Event()
def on_state(state: FirmwareState) -> None:
if state.safety is not None and state.safety.armed:
armed.set()
unsubscribe = self.on_state(on_state)
try:
if not armed.wait(timeout if timeout > 0 else None):
raise TimeoutError(f'the robot was not armed within {timeout} seconds')
finally:
unsubscribe()
[docs]
def start(self) -> None:
"""
Start the firmware container.
"""
self._http_client.post(paths_gen.Firmware.FIRMWARE_START, transport_models.EmptyPayload,
transport_models.EmptyPayload())
[docs]
def stop(self) -> None:
"""
Stop the firmware container.
"""
self._http_client.post(paths_gen.Firmware.FIRMWARE_STOP, transport_models.EmptyPayload,
transport_models.EmptyPayload())
[docs]
def restart(self) -> None:
"""
Restart the firmware container.
"""
self._http_client.post(paths_gen.Firmware.FIRMWARE_RESTART, transport_models.EmptyPayload,
transport_models.EmptyPayload())
[docs]
def on_state(self, callback: FirmwareStateCallback) -> Unsubscribe:
"""
Set a callback to be called when the firmware state changes.
:param callback: The callback function that takes a FirmwareState parameter.
"""
def internal_callback(_: str, state: transport_models.a.FirmwareState) -> None:
callback(FirmwareState.from_a_transport_model(state))
return self._mqtt_client.subscribe(topics_gen.FirmwareState.FIRMWARE_STATE, internal_callback,
transport_models.a.FirmwareState)
[docs]
def on_plc_safety_state(self, callback: PlcSafetyStateCallback) -> Unsubscribe:
"""
Set a callback to be called when the PLC safety state changes.
:param callback: The callback function that takes a PlcSafetyState parameter.
"""
def internal_callback(_: str, state: transport_models.a.PlcSafetyState) -> None:
callback(PlcSafetyState.from_transport_model(state))
return self._mqtt_client.subscribe(topics_gen.PlcSafety.PLC_SAFETY, internal_callback,
transport_models.a.PlcSafetyState)