petal-user-journey-coordinator 0.1.7__tar.gz → 0.1.9__tar.gz
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.
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/PKG-INFO +1 -1
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/pyproject.toml +1 -1
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/data_model.py +23 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/plugin.py +147 -38
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/README.md +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/__init__.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/controllers.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/__init__.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/dummy_trajectory_test.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/fix_expected_results.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/generate_trajectory_test_data.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/generated_trajectory_test_data.json +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/test_generated_trajectories.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/test_petal_user_journey_coordinator.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/test_plotting.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/test_trajectory_simple.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/test_trajectory_verification.py +0 -0
- {petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/trajectory_test_data.json +0 -0
|
@@ -633,4 +633,27 @@ class BulkParameterResponse(BaseModel):
|
|
|
633
633
|
"timestamp": "2023-01-01T00:00:00Z"
|
|
634
634
|
}
|
|
635
635
|
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
class RebootPX4StatusPayload(BaseModel):
|
|
640
|
+
"""Payload for PX4 reboot status published via MQTT after reboot completes."""
|
|
641
|
+
reboot_initiated: bool = Field(..., description="Whether the reboot command was initiated")
|
|
642
|
+
reboot_success: Optional[bool] = Field(default=None, description="Whether the reboot completed successfully")
|
|
643
|
+
status: Literal["success", "failed"] = Field(..., description="Reboot status")
|
|
644
|
+
message: str = Field(..., description="Human-readable status message")
|
|
645
|
+
error_code: Optional[str] = Field(default=None, description="Error code if reboot failed")
|
|
646
|
+
timestamp: datetime = Field(..., description="Timestamp of the status update")
|
|
647
|
+
|
|
648
|
+
model_config = {
|
|
649
|
+
"json_schema_extra": {
|
|
650
|
+
"example": {
|
|
651
|
+
"reboot_initiated": True,
|
|
652
|
+
"reboot_success": True,
|
|
653
|
+
"status": "success",
|
|
654
|
+
"message": "PX4 reboot completed successfully",
|
|
655
|
+
"error_code": None,
|
|
656
|
+
"timestamp": "2026-01-07T12:00:00Z"
|
|
657
|
+
}
|
|
658
|
+
}
|
|
636
659
|
}
|
|
@@ -93,7 +93,10 @@ from .data_model import (
|
|
|
93
93
|
OpticalFlowModulePayload,
|
|
94
94
|
BulkParameterSetRequest,
|
|
95
95
|
BulkParameterGetRequest,
|
|
96
|
-
BulkParameterResponse
|
|
96
|
+
BulkParameterResponse,
|
|
97
|
+
|
|
98
|
+
# Reboot status payload
|
|
99
|
+
RebootPX4StatusPayload
|
|
97
100
|
)
|
|
98
101
|
|
|
99
102
|
from petal_app_manager.models.mavlink import (
|
|
@@ -1711,7 +1714,18 @@ class PetalUserJourneyCoordinator(Petal):
|
|
|
1711
1714
|
results[pname] = confirmed[pname]
|
|
1712
1715
|
# check that the set value matches the requested value
|
|
1713
1716
|
confirmed_value = results[pname].get("value")
|
|
1714
|
-
|
|
1717
|
+
requested_value = parameter.parameter_value
|
|
1718
|
+
|
|
1719
|
+
# Check for equality, handling floating point precision issues
|
|
1720
|
+
is_match = False
|
|
1721
|
+
if isinstance(confirmed_value, (float, int)) and isinstance(requested_value, (float, int)):
|
|
1722
|
+
# Use 1e-5 relative tolerance to handle float32/float64 mismatch
|
|
1723
|
+
# This should be enough for values like 0.2 vs 0.20000000298...
|
|
1724
|
+
is_match = math.isclose(confirmed_value, requested_value, rel_tol=1e-5)
|
|
1725
|
+
else:
|
|
1726
|
+
is_match = confirmed_value == requested_value
|
|
1727
|
+
|
|
1728
|
+
if is_match:
|
|
1715
1729
|
results[pname]["success"] = True
|
|
1716
1730
|
else:
|
|
1717
1731
|
results[pname]["success"] = False
|
|
@@ -1929,13 +1943,22 @@ class PetalUserJourneyCoordinator(Petal):
|
|
|
1929
1943
|
|
|
1930
1944
|
@http_action(method="POST", path="/mqtt/reboot_px4")
|
|
1931
1945
|
async def _reboot_px4_message_handler(self, topic: str, message: Dict[str, Any]):
|
|
1932
|
-
"""
|
|
1946
|
+
"""
|
|
1947
|
+
Handle reboot PX4 MQTT messages.
|
|
1948
|
+
|
|
1949
|
+
Flow:
|
|
1950
|
+
1. Validate message and check for active operations
|
|
1951
|
+
2. Immediately send_command_response to acknowledge command received
|
|
1952
|
+
3. Await reboot_autopilot to execute the reboot
|
|
1953
|
+
4. Publish reboot status via MQTT publish_message
|
|
1954
|
+
"""
|
|
1955
|
+
mqtt_msg = None
|
|
1933
1956
|
try:
|
|
1934
|
-
# Parse
|
|
1957
|
+
# Parse and validate MQTT message
|
|
1935
1958
|
mqtt_msg = MQTTMessage(**message)
|
|
1936
1959
|
message_id = mqtt_msg.messageId
|
|
1937
1960
|
|
|
1938
|
-
# Check if controller
|
|
1961
|
+
# Check if any controller has an active operation
|
|
1939
1962
|
for controller in self._active_controllers.values():
|
|
1940
1963
|
if controller is not None and controller.is_active:
|
|
1941
1964
|
error_msg = "PX4 reboot blocked - Active operation in progress"
|
|
@@ -1949,61 +1972,137 @@ class PetalUserJourneyCoordinator(Petal):
|
|
|
1949
1972
|
"error_code": "OPERATION_ACTIVE"
|
|
1950
1973
|
}
|
|
1951
1974
|
)
|
|
1952
|
-
|
|
1953
1975
|
return
|
|
1954
|
-
|
|
1955
|
-
logger.info(f"Restarting PX4 for {self.name} petal")
|
|
1956
|
-
reboot_response = await self._mavlink_proxy.reboot_autopilot(
|
|
1957
|
-
reboot_onboard_computer=False,
|
|
1958
|
-
timeout=5.0
|
|
1959
|
-
)
|
|
1960
|
-
|
|
1961
|
-
if not reboot_response.success:
|
|
1962
|
-
error_msg = "PX4 reboot command failed or timed out"
|
|
1963
|
-
logger.error(f"[{message_id}] {error_msg}")
|
|
1964
|
-
if mqtt_msg.waitResponse:
|
|
1965
|
-
await self._mqtt_proxy.send_command_response(
|
|
1966
|
-
message_id=message_id,
|
|
1967
|
-
response_data={
|
|
1968
|
-
"status": "error",
|
|
1969
|
-
"message": error_msg,
|
|
1970
|
-
"error_code": "REBOOT_FAILED",
|
|
1971
|
-
"data": _json_safe(reboot_response.model_dump())
|
|
1972
|
-
}
|
|
1973
|
-
)
|
|
1974
|
-
return
|
|
1975
|
-
|
|
1976
|
-
logger.info(f"[{message_id}] PX4 reboot command successful")
|
|
1977
1976
|
|
|
1977
|
+
# Step 1: Immediately respond to acknowledge command received
|
|
1978
|
+
logger.info(f"[{message_id}] PX4 reboot command received, sending acknowledgement")
|
|
1978
1979
|
if mqtt_msg.waitResponse:
|
|
1979
1980
|
await self._mqtt_proxy.send_command_response(
|
|
1980
1981
|
message_id=message_id,
|
|
1981
1982
|
response_data={
|
|
1982
1983
|
"status": "success",
|
|
1983
|
-
"message": "PX4 reboot command
|
|
1984
|
-
"data":
|
|
1984
|
+
"message": "PX4 reboot command initiated",
|
|
1985
|
+
"data": {
|
|
1986
|
+
"reboot_initiated": True,
|
|
1987
|
+
"message": "Reboot in progress, status will be published to command/web"
|
|
1988
|
+
}
|
|
1985
1989
|
}
|
|
1986
1990
|
)
|
|
1991
|
+
|
|
1987
1992
|
except ValidationError as ve:
|
|
1988
|
-
error_msg = f"Invalid PX4 reboot
|
|
1993
|
+
error_msg = f"Invalid PX4 reboot message: {ve}"
|
|
1989
1994
|
logger.error(f"PX4 reboot validation error: {error_msg}")
|
|
1990
|
-
if mqtt_msg.waitResponse:
|
|
1995
|
+
if mqtt_msg and mqtt_msg.waitResponse:
|
|
1991
1996
|
await self._mqtt_proxy.send_command_response(
|
|
1992
|
-
message_id=
|
|
1997
|
+
message_id=mqtt_msg.messageId,
|
|
1993
1998
|
response_data={
|
|
1994
1999
|
"status": "error",
|
|
1995
2000
|
"message": error_msg,
|
|
1996
2001
|
"error_code": "VALIDATION_ERROR"
|
|
1997
2002
|
}
|
|
1998
2003
|
)
|
|
2004
|
+
return
|
|
1999
2005
|
except Exception as e:
|
|
2000
|
-
|
|
2006
|
+
error_msg = f"PX4 reboot message handler error: {str(e)}"
|
|
2007
|
+
logger.error(f"Unexpected PX4 reboot error: {error_msg}")
|
|
2001
2008
|
if mqtt_msg and mqtt_msg.waitResponse:
|
|
2002
2009
|
await self._mqtt_proxy.send_command_response(
|
|
2003
2010
|
message_id=mqtt_msg.messageId,
|
|
2004
|
-
response_data={
|
|
2011
|
+
response_data={
|
|
2012
|
+
"status": "error",
|
|
2013
|
+
"message": error_msg,
|
|
2014
|
+
"error_code": "HANDLER_ERROR"
|
|
2015
|
+
}
|
|
2005
2016
|
)
|
|
2006
|
-
|
|
2017
|
+
return
|
|
2018
|
+
|
|
2019
|
+
try:
|
|
2020
|
+
# Step 2: Await the actual reboot operation
|
|
2021
|
+
logger.info(f"[{message_id}] Executing PX4 reboot for {self.name} petal")
|
|
2022
|
+
reboot_response: RebootAutopilotResponse = await self._mavlink_proxy.reboot_autopilot(
|
|
2023
|
+
reboot_onboard_computer=False,
|
|
2024
|
+
timeout=5.0
|
|
2025
|
+
)
|
|
2026
|
+
|
|
2027
|
+
# Step 3: Build and publish reboot status via MQTT
|
|
2028
|
+
if reboot_response.success:
|
|
2029
|
+
logger.info(f"[{message_id}] PX4 reboot successful: {reboot_response.reason}")
|
|
2030
|
+
status_payload = RebootPX4StatusPayload(
|
|
2031
|
+
reboot_initiated=True,
|
|
2032
|
+
reboot_success=True,
|
|
2033
|
+
status="success",
|
|
2034
|
+
message=reboot_response.reason,
|
|
2035
|
+
error_code=None,
|
|
2036
|
+
timestamp=datetime.now(timezone.utc)
|
|
2037
|
+
)
|
|
2038
|
+
else:
|
|
2039
|
+
logger.error(f"[{message_id}] PX4 reboot failed: {reboot_response.reason}")
|
|
2040
|
+
status_payload = RebootPX4StatusPayload(
|
|
2041
|
+
reboot_initiated=True,
|
|
2042
|
+
reboot_success=False,
|
|
2043
|
+
status="failed",
|
|
2044
|
+
message=reboot_response.reason,
|
|
2045
|
+
error_code=reboot_response.status_code.value,
|
|
2046
|
+
timestamp=datetime.now(timezone.utc)
|
|
2047
|
+
)
|
|
2048
|
+
|
|
2049
|
+
# Publish reboot status via MQTT
|
|
2050
|
+
mqtt_message = {
|
|
2051
|
+
"messageId": message_id,
|
|
2052
|
+
"command": f"/{self.name}/reboot_px4_status",
|
|
2053
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
2054
|
+
"payload": status_payload.model_dump(mode="json")
|
|
2055
|
+
}
|
|
2056
|
+
await self._mqtt_proxy.publish_message(payload=mqtt_message)
|
|
2057
|
+
logger.info(f"[{message_id}] Published reboot status to command/web")
|
|
2058
|
+
|
|
2059
|
+
except ValidationError as ve:
|
|
2060
|
+
error_msg = f"PX4 reboot status payload validation error: {ve}"
|
|
2061
|
+
logger.error(f"[{mqtt_msg.messageId if mqtt_msg else 'unknown'}] {error_msg}")
|
|
2062
|
+
# Try to publish error status if we got past validation
|
|
2063
|
+
if mqtt_msg:
|
|
2064
|
+
try:
|
|
2065
|
+
error_payload = RebootPX4StatusPayload(
|
|
2066
|
+
reboot_initiated=True,
|
|
2067
|
+
reboot_success=False,
|
|
2068
|
+
status="failed",
|
|
2069
|
+
message=error_msg,
|
|
2070
|
+
error_code="PAYLOAD_VALIDATION_ERROR",
|
|
2071
|
+
timestamp=datetime.now(timezone.utc)
|
|
2072
|
+
)
|
|
2073
|
+
mqtt_message = {
|
|
2074
|
+
"messageId": mqtt_msg.messageId,
|
|
2075
|
+
"command": f"/{self.name}/reboot_px4_status",
|
|
2076
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
2077
|
+
"payload": error_payload.model_dump(mode="json")
|
|
2078
|
+
}
|
|
2079
|
+
await self._mqtt_proxy.publish_message(payload=mqtt_message)
|
|
2080
|
+
except Exception as publish_error:
|
|
2081
|
+
logger.error(f"[{mqtt_msg.messageId}] Failed to publish error status: {publish_error}")
|
|
2082
|
+
|
|
2083
|
+
except Exception as e:
|
|
2084
|
+
error_msg = f"PX4 reboot error: {str(e)}"
|
|
2085
|
+
logger.error(f"[{mqtt_msg.messageId if mqtt_msg else 'unknown'}] {error_msg}")
|
|
2086
|
+
# Try to publish error status if we got past validation
|
|
2087
|
+
if mqtt_msg:
|
|
2088
|
+
try:
|
|
2089
|
+
error_payload = RebootPX4StatusPayload(
|
|
2090
|
+
reboot_initiated=True,
|
|
2091
|
+
reboot_success=False,
|
|
2092
|
+
status="failed",
|
|
2093
|
+
message=error_msg,
|
|
2094
|
+
error_code="EXECUTION_ERROR",
|
|
2095
|
+
timestamp=datetime.now(timezone.utc)
|
|
2096
|
+
)
|
|
2097
|
+
mqtt_message = {
|
|
2098
|
+
"messageId": mqtt_msg.messageId,
|
|
2099
|
+
"command": f"/{self.name}/reboot_px4_status",
|
|
2100
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
2101
|
+
"payload": error_payload.model_dump(mode="json")
|
|
2102
|
+
}
|
|
2103
|
+
await self._mqtt_proxy.publish_message(payload=mqtt_message)
|
|
2104
|
+
except Exception as publish_error:
|
|
2105
|
+
logger.error(f"[{mqtt_msg.messageId}] Failed to publish error status: {publish_error}")
|
|
2007
2106
|
|
|
2008
2107
|
def _create_parameter_message_handler(self, handler_key: str, config_type: str):
|
|
2009
2108
|
"""
|
|
@@ -2589,7 +2688,17 @@ class PetalUserJourneyCoordinator(Petal):
|
|
|
2589
2688
|
results[pname] = confirmed[pname]
|
|
2590
2689
|
# check that the set value matches the requested value
|
|
2591
2690
|
confirmed_value = results[pname].get("value")
|
|
2592
|
-
|
|
2691
|
+
requested_value = parameter.parameter_value
|
|
2692
|
+
|
|
2693
|
+
# Check for equality, handling floating point precision issues
|
|
2694
|
+
is_match = False
|
|
2695
|
+
if isinstance(confirmed_value, (float, int)) and isinstance(requested_value, (float, int)):
|
|
2696
|
+
# Use 1e-5 relative tolerance to handle float32/float64 mismatch
|
|
2697
|
+
is_match = math.isclose(confirmed_value, requested_value, rel_tol=1e-5)
|
|
2698
|
+
else:
|
|
2699
|
+
is_match = confirmed_value == requested_value
|
|
2700
|
+
|
|
2701
|
+
if is_match:
|
|
2593
2702
|
results[pname]["success"] = True
|
|
2594
2703
|
else:
|
|
2595
2704
|
results[pname]["success"] = False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{petal_user_journey_coordinator-0.1.7 → petal_user_journey_coordinator-0.1.9}/tests/test_plotting.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|