petal-user-journey-coordinator 0.1.8__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.
Files changed (18) hide show
  1. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/PKG-INFO +1 -1
  2. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/pyproject.toml +1 -1
  3. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/data_model.py +23 -0
  4. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/plugin.py +124 -36
  5. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/README.md +0 -0
  6. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/__init__.py +0 -0
  7. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/src/petal_user_journey_coordinator/controllers.py +0 -0
  8. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/__init__.py +0 -0
  9. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/dummy_trajectory_test.py +0 -0
  10. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/fix_expected_results.py +0 -0
  11. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/generate_trajectory_test_data.py +0 -0
  12. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/generated_trajectory_test_data.json +0 -0
  13. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/test_generated_trajectories.py +0 -0
  14. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/test_petal_user_journey_coordinator.py +0 -0
  15. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/test_plotting.py +0 -0
  16. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/test_trajectory_simple.py +0 -0
  17. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/test_trajectory_verification.py +0 -0
  18. {petal_user_journey_coordinator-0.1.8 → petal_user_journey_coordinator-0.1.9}/tests/trajectory_test_data.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: petal-user-journey-coordinator
3
- Version: 0.1.8
3
+ Version: 0.1.9
4
4
  Summary: A petal for the DroneLeaf ecosystem
5
5
  Author-Email: Khalil Al Handawi <khalil.alhandawi@droneleaf.io>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "petal-user-journey-coordinator"
3
- version = "0.1.8"
3
+ version = "0.1.9"
4
4
  description = "A petal for the DroneLeaf ecosystem"
5
5
  authors = [
6
6
  { name = "Khalil Al Handawi", email = "khalil.alhandawi@droneleaf.io" },
@@ -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 (
@@ -1940,13 +1943,22 @@ class PetalUserJourneyCoordinator(Petal):
1940
1943
 
1941
1944
  @http_action(method="POST", path="/mqtt/reboot_px4")
1942
1945
  async def _reboot_px4_message_handler(self, topic: str, message: Dict[str, Any]):
1943
- """Handle reboot PX4 MQTT messages."""
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
1944
1956
  try:
1945
- # Parse base MQTT message
1957
+ # Parse and validate MQTT message
1946
1958
  mqtt_msg = MQTTMessage(**message)
1947
1959
  message_id = mqtt_msg.messageId
1948
1960
 
1949
- # Check if controller is in emergency mode
1961
+ # Check if any controller has an active operation
1950
1962
  for controller in self._active_controllers.values():
1951
1963
  if controller is not None and controller.is_active:
1952
1964
  error_msg = "PX4 reboot blocked - Active operation in progress"
@@ -1960,61 +1972,137 @@ class PetalUserJourneyCoordinator(Petal):
1960
1972
  "error_code": "OPERATION_ACTIVE"
1961
1973
  }
1962
1974
  )
1963
-
1964
1975
  return
1965
-
1966
- logger.info(f"Restarting PX4 for {self.name} petal")
1967
- reboot_response = await self._mavlink_proxy.reboot_autopilot(
1968
- reboot_onboard_computer=False,
1969
- timeout=5.0
1970
- )
1971
-
1972
- if not reboot_response.success:
1973
- error_msg = "PX4 reboot command failed or timed out"
1974
- logger.error(f"[{message_id}] {error_msg}")
1975
- if mqtt_msg.waitResponse:
1976
- await self._mqtt_proxy.send_command_response(
1977
- message_id=message_id,
1978
- response_data={
1979
- "status": "error",
1980
- "message": error_msg,
1981
- "error_code": "REBOOT_FAILED",
1982
- "data": _json_safe(reboot_response.model_dump())
1983
- }
1984
- )
1985
- return
1986
-
1987
- logger.info(f"[{message_id}] PX4 reboot command successful")
1988
1976
 
1977
+ # Step 1: Immediately respond to acknowledge command received
1978
+ logger.info(f"[{message_id}] PX4 reboot command received, sending acknowledgement")
1989
1979
  if mqtt_msg.waitResponse:
1990
1980
  await self._mqtt_proxy.send_command_response(
1991
1981
  message_id=message_id,
1992
1982
  response_data={
1993
1983
  "status": "success",
1994
- "message": "PX4 reboot command successful",
1995
- "data": _json_safe(reboot_response.model_dump())
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
+ }
1996
1989
  }
1997
1990
  )
1991
+
1998
1992
  except ValidationError as ve:
1999
- error_msg = f"Invalid PX4 reboot payload: {ve}"
1993
+ error_msg = f"Invalid PX4 reboot message: {ve}"
2000
1994
  logger.error(f"PX4 reboot validation error: {error_msg}")
2001
- if mqtt_msg.waitResponse:
1995
+ if mqtt_msg and mqtt_msg.waitResponse:
2002
1996
  await self._mqtt_proxy.send_command_response(
2003
- message_id=message_id,
1997
+ message_id=mqtt_msg.messageId,
2004
1998
  response_data={
2005
1999
  "status": "error",
2006
2000
  "message": error_msg,
2007
2001
  "error_code": "VALIDATION_ERROR"
2008
2002
  }
2009
2003
  )
2004
+ return
2010
2005
  except Exception as e:
2011
- logger.error(f"Error handling PX4 reboot message: {e}")
2006
+ error_msg = f"PX4 reboot message handler error: {str(e)}"
2007
+ logger.error(f"Unexpected PX4 reboot error: {error_msg}")
2012
2008
  if mqtt_msg and mqtt_msg.waitResponse:
2013
2009
  await self._mqtt_proxy.send_command_response(
2014
2010
  message_id=mqtt_msg.messageId,
2015
- response_data={"status": "error", "message": str(e)}
2011
+ response_data={
2012
+ "status": "error",
2013
+ "message": error_msg,
2014
+ "error_code": "HANDLER_ERROR"
2015
+ }
2016
2016
  )
2017
- return
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}")
2018
2106
 
2019
2107
  def _create_parameter_message_handler(self, handler_key: str, config_type: str):
2020
2108
  """