@@ -309,6 +309,9 @@ def __init__(self, robot_id, robot_name, api_key=None, **kwargs) -> None:
309309 self .map_data_mutex = threading .Lock ()
310310 self .map_files : dict [str , RobotMap ] = {} # label to map data
311311
312+ # Callback for determining robot online status
313+ self ._online_status_callback = None
314+
312315 self .message_handlers [MQTT_INITIAL_POSE ] = self ._handle_initial_pose
313316 self .message_handlers [MQTT_CUSTOM_COMMAND ] = self ._handle_custom_command
314317 self .message_handlers [MQTT_CUSTOM_COMMAND_MESSAGE ] = self ._handle_custom_message
@@ -473,11 +476,10 @@ def _on_connect(self, client, userdata, flags, reason_code, properties):
473476 )
474477 return
475478
476- # Send robot online status.
477- # This method is blocking so do it on a separate thread just in case.
478- threading .Thread (
479- target = self ._send_robot_status , kwargs = {"robot_status" : "1" }
480- ).start ()
479+ # Send robot online status (best effort)
480+ # If this fails and InOrbit is getting data, it will detect discrepancy
481+ # and request a status update via get_state command.
482+ self ._send_robot_status (online = True )
481483
482484 # Subscribe to interesting topics
483485 self .client .subscribe (
@@ -620,6 +622,8 @@ def _handle_in_cmd(self, msg):
620622 self ._handle_load_module (args [1 ], args [2 ])
621623 if args [0 ] == "unload_module" and len (args ) >= 2 :
622624 self ._handle_unload_module (args [1 ])
625+ if args [0 ] == "get_state" :
626+ self ._handle_get_state ()
623627
624628 def _handle_load_module (self , module_name , run_level ):
625629 """Handles a load_module command"""
@@ -631,6 +635,22 @@ def _handle_unload_module(self, module_name):
631635 if module_name == INORBIT_MODULE_CAMERAS :
632636 self ._stop_cameras_streaming ()
633637
638+ def _handle_get_state (self ):
639+ """Handle get_state command from InOrbit."""
640+ is_online = True # Default assumption
641+
642+ if self ._online_status_callback :
643+ try :
644+ is_online = self ._online_status_callback ()
645+ except Exception as e :
646+ self .logger .error (f"Online status callback failed: { e } " )
647+ # Fall back to default (True) on callback error
648+
649+ self ._send_robot_status (online = is_online )
650+ self .logger .debug (
651+ f"Responded to get_state: robot { 'online' if is_online else 'offline' } "
652+ )
653+
634654 def _start_cameras_streaming (self ):
635655 """Start streaming on all registered cameras"""
636656 with self .camera_streaming_mutex :
@@ -893,6 +913,17 @@ def register_command_callback(self, callback):
893913
894914 self .command_callbacks .append (callback )
895915
916+ def set_online_status_callback (self , callback ):
917+ """Set callback to determine robot online status.
918+
919+ Args:
920+ callback: A callable that returns bool indicating if robot is online.
921+ Should return True if robot is online, False otherwise.
922+ Will be called when InOrbit requests status via get_state command.
923+ """
924+ if callable (callback ):
925+ self ._online_status_callback = callback
926+
896927 def unregister_command_callback (self , callback ):
897928 """Unregisters the specified callback"""
898929 # TODO: Implement
@@ -922,48 +953,36 @@ def _resend_modules(self):
922953 qos = 1 ,
923954 )
924955
925- def _send_robot_status (self , robot_status ):
926- """Sends robot online/offline status message.
927-
928- This method blocks until either the message
929- is sent or the client errors out.
956+ def _send_robot_status (self , online = True ):
957+ """Send robot online/offline status (best effort, non-blocking).
930958
931959 Args:
932- robot_status (Union[bool,str]): Connection status
933- It supports ``bool`` and ``str`` values ("0" or "1")
934-
935- Raises:
936- ValueError: on invalid ``robot_status``
960+ online (bool): True for online status, False for offline status.
937961 """
962+ status_value = "1" if online else "0"
963+ status_str = "online" if online else "offline"
938964
939- # Validate ``robot_status`` parameter.
940- if isinstance (robot_status , bool ):
941- robot_status = "1" if robot_status else "0"
942-
943- if robot_status not in ["0" , "1" ]:
944- raise ValueError ("Robot status must be boolean, '0' or '1'" )
945-
946- # Every time we connect or disconnect to the service, send
947- # updated status including online/offline bit
948- status_message = "{}|{}|{}|{}" .format (
949- robot_status , self .robot_api_key , self .agent_version , self .robot_name
950- )
951- ret = self .publish (
952- self ._get_robot_subtopic (subtopic = MQTT_SUBTOPIC_STATE ),
953- status_message ,
954- qos = 1 ,
955- retain = True ,
956- )
957- self .logger .info ("Publishing status {}. ret = {}." .format (robot_status , ret ))
958-
959- # TODO: handle errors while waiting for publish. Consider that
960- # this method would typically run on a separate thread.
961- ret .wait_for_publish ()
962- published = ret .is_published ()
963-
964- self .logger .info (
965- "Robot status '{}' published: {:b}." .format (robot_status , published )
966- )
965+ try :
966+ status_message = (
967+ f"{ status_value } |{ self .robot_api_key } |"
968+ f"{ self .agent_version } |{ self .robot_name } "
969+ )
970+ self .client .publish (
971+ self ._get_robot_subtopic (subtopic = MQTT_SUBTOPIC_STATE ),
972+ status_message ,
973+ qos = 1 ,
974+ retain = True ,
975+ )
976+ self .logger .debug (f"{ status_str .capitalize ()} status sent successfully" )
977+ except Exception as e :
978+ fallback_msg = (
979+ "InOrbit will detect via data messages"
980+ if online
981+ else "InOrbit will detect via data absence"
982+ )
983+ self .logger .debug (
984+ f"{ status_str .capitalize ()} status failed: { e } - { fallback_msg } "
985+ )
967986
968987 def _is_connected (self ):
969988 return self .client .is_connected ()
@@ -1036,7 +1055,10 @@ def disconnect(self):
10361055 """Ends session, disconnecting from cloud services"""
10371056 self .logger .info ("Ending robot session" )
10381057 self ._stop_cameras_streaming ()
1039- self ._send_robot_status (robot_status = "0" )
1058+
1059+ # Send offline status (best effort, non-blocking)
1060+ # InOrbit will detect offline via data absence if this fails
1061+ self ._send_robot_status (online = False )
10401062
10411063 # TODO: Unsubscribe from topics
10421064
0 commit comments