Robot-side state modules (on_state) reference

Robot-side state modules (on_state) reference

The robot reports its own state to the SDK via the agentsdk.state_request.meta event. Inside the passive callback you receive:

text
PassiveCallback.on_state(agent_id, event_id, state_name, state_value)
                          ^^^^^^^^^^                ^^^^^^^^^^^^^^^^
                          module name              module payload (JSON-encoded string)
LanguageOverride entry
JavaPassiveCallback.onState(agentId, eventId, stateName, stateValue)
PythonPassiveCallback.on_state(agent_id, event_id, state_name, state_value)

⚠️ state_value may be a plain string, a number, an object, or an array. Parse with JSONObject.parseObject(stateValue) / json.loads(state_value) when you need structured fields.

This page enumerates every state_name and the field schema of its state_value — 22 modules total. Each module ships with a JSON sample for direct parsing reference.


Module index

state_nameTopicFrequency
map_infoMap basics + navigation pointslow / event
map_2d_grid2-D occupancy-grid snapshotvery low
current_locationCurrent semantic locationevent
robot_poseGeometric posehigh (capped at 10 Hz)
planned_pathCurrent planned pathevent
locomotion_statusNavigation execution stateevent
pnc_statusPlanning / control / avoidance summaryevent
task_statusTask state listevent
exhibit_tasksExhibit-tour task listevent
silisoonBusiness mode + state extensionevent
robot_formRobot form-factorone-off
manipulationArm / grasp / manipulation statusevent
supported_calligraphy_listWritable calligraphy charactersone-off
tts_statusTTS runtime statusevent
vad_statusVAD runtime statusevent
dialog_statusDialog state machineevent
action_statusAction / skill task statusevent
listening_statusListening / wake stateevent
wakeup_behavior_statusWake-up behaviour override statusevent
vision_perceptionVision perception summaryhigh (capped at 10 Hz)
speakerCurrent speaker infoevent
settingsOn-device toggle panelevent

map_info

Map basics + navigation points.

FieldTypeRequiredDescription
map_idstring / numberNoMap ID
map_namestringNoMap name
navi_pointsarrayNoNavigation point list
wallsarrayNoWall position list
infeasible_areasarrayNoVirtual wall position list

walls / infeasible_areas element:

FieldTypeRequiredDescription
idnumberYesPoint ID
xnumberNoX coordinate
ynumberNoY coordinate
znumberNoZ coordinate

navi_points element:

FieldTypeRequiredDescription
namestringYesPoint name
idnumberYesPoint ID
point_xnumberNoX coordinate
point_ynumberNoY coordinate

JSON sample:

json
{
    "map_id": "MAP_001",
    "map_name": "1F Lobby",
    "navi_points": [
        { "name": "reception", "id": 1, "point_x": 1.2, "point_y": 3.4 },
        { "name": "pantry", "id": 2, "point_x": 5.6, "point_y": 7.8 }
    ],
    "walls": [
        { "id": 101, "x": 0.0, "y": 0.0, "z": 0.0 },
        { "id": 102, "x": 10.0, "y": 0.0, "z": 0.0 }
    ],
    "infeasible_areas": [
        { "id": 201, "x": 2.5, "y": 3.0, "z": 0.0 }
    ]
}

map_2d_grid

2-D occupancy-grid snapshot — published infrequently: only on map switch, version bump, first full sync, or after receiving session.input_status_info.sync.

Recommended encoding:

  • Raw layout: int8 raw
  • Compression: zstd
  • Text wrapping: base64
  • Combined encoding: int8_zstd_base64
FieldTypeRequiredDescription
map_idstring / numberYesMap ID
map_namestringNoMap name
versionstringNoMap version
framestringNoFrame, e.g. map
widthnumberYesGrid width (cells)
heightnumberYesGrid height (cells)
resolutionnumberYesmetres / cell
originobjectYesOrigin with x / y / yaw
encodingstringYesPrefer int8_zstd_base64; int8_raw for debugging
datastringNoCompressed + base64-encoded grid bytes; may be omitted if transport.mode=resource_ref
data_md5stringNoMap content digest
compressionobjectNoBulk-data compression metadata
transportobjectNoBulk-data transport metadata

compression:

FieldTypeRequiredDescription
raw_layoutstringNoe.g. int8_row_major
algorithmstringNoPrefer zstd
binary_encodingstringNoPrefer base64

transport:

FieldTypeRequiredDescription
modestringNoinline / chunked / resource_ref
resource_uristringNoMap URI when using resource_ref mode
chunk_indexnumberNoChunk index, starting from 0
chunk_countnumberNoTotal chunk count

JSON sample:

json
{
    "map_id": "MAP_001",
    "map_name": "1F Lobby",
    "version": "v2",
    "frame": "map",
    "width": 1024,
    "height": 1024,
    "resolution": 0.05,
    "origin": { "x": -25.6, "y": -25.6, "yaw": 0.0 },
    "encoding": "int8_zstd_base64",
    "data": "KLUv/QBYDQAA...<base64>...",
    "data_md5": "9e107d9d372bb6826bd81d3542a419d6",
    "compression": {
        "raw_layout": "int8_row_major",
        "algorithm": "zstd",
        "binary_encoding": "base64"
    },
    "transport": { "mode": "inline" }
}

current_location

Current semantic location (high-frequency geometric pose goes to robot_pose).

FieldTypeRequiredDescription
location_namestringNoLocation name
location_coordinatestringNoCoordinate string, recommended "x,y"
orientationnumber / nullNoHeading angle (degrees recommended)
floorstring / numberNoFloor
buildingstringNoBuilding / venue

The SDK's compatibility shim current_location.upload accepts both {"current_location": {...}} and the bare {"location_name": "..."}.

JSON sample:

json
{
    "location_name": "reception",
    "location_coordinate": "1.2,3.4",
    "orientation": 90,
    "floor": "1F",
    "building": "HQ Tower"
}

robot_pose

Current geometric pose.

FieldTypeRequiredDescription
map_idstring / numberNoOwning map ID
framestringNomap / odom / ...
xnumberYesX coordinate
ynumberYesY coordinate
znumberNoZ coordinate
yawnumberYesYaw (radians recommended)
pitchnumberNoPitch
rollnumberNoRoll
quaternionobjectNoQuaternion
linear_velocityobjectNoLinear velocity
angular_velocityobjectNoAngular velocity
timestamp_msnumberNoSample timestamp

High-frequency geometric state — the SDK's input_status_info upload layer caps per-module output at 10 Hz by default. The cap only throttles outbound traffic; the local cache always retains the most recent write.

JSON sample:

json
{
    "map_id": "MAP_001",
    "frame": "map",
    "x": 1.234,
    "y": 5.678,
    "z": 0.0,
    "yaw": 1.5708,
    "pitch": 0.0,
    "roll": 0.0,
    "quaternion": { "w": 0.7071, "x": 0.0, "y": 0.0, "z": 0.7071 },
    "linear_velocity": { "x": 0.3, "y": 0.0, "z": 0.0 },
    "angular_velocity": { "x": 0.0, "y": 0.0, "z": 0.1 },
    "timestamp_ms": 1774828800210
}

planned_path

Current planned path; replace the whole array on each update, never merge point-by-point.

FieldTypeRequiredDescription
path_idstringNoPath ID
map_idstring / numberNoOwning map ID
framestringNoFrame
plannerstringNoPlanner name
planning_statusstringYesrunning / ready / replanned / blocked / failed
start_poseobjectNoStart pose
target_poseobjectNoTarget pose
path_pointsarrayYesPath points
total_length_mnumberNoTotal length
remaining_length_mnumberNoRemaining length
eta_secnumberNoETA in seconds
updated_at_msnumberNoLast update timestamp

JSON sample:

json
{
    "path_id": "path_001",
    "map_id": "MAP_001",
    "frame": "map",
    "planner": "global_planner_v2",
    "planning_status": "running",
    "start_pose": { "x": 0.0, "y": 0.0, "yaw": 0.0 },
    "target_pose": { "x": 10.0, "y": 5.0, "yaw": 1.5708 },
    "path_points": [
        { "x": 0.0, "y": 0.0 },
        { "x": 2.5, "y": 1.2 },
        { "x": 5.0, "y": 2.5 },
        { "x": 7.5, "y": 3.8 },
        { "x": 10.0, "y": 5.0 }
    ],
    "total_length_m": 11.18,
    "remaining_length_m": 8.94,
    "eta_sec": 30,
    "updated_at_ms": 1774828800210
}

locomotion_status

Current navigation execution status.

FieldTypeRequiredDescription
motion_task_idstring / numberNoMotion task ID
task_namestringNoTask name
task_typestringNoFixed to navigation for the first phase
statestringYeswaiting / running / paused / completed / failed
target_xnumberNoTarget X
target_ynumberNoTarget Y
target_znumberNoTarget Z
start_time_msnumberNoStart time
estimated_completion_msnumberNoETA timestamp
timestamp_msnumberNoUpdate time
trace_idstringNoTrace ID
error_messagestringNoError reason

JSON sample:

json
{
    "motion_task_id": "nav_001",
    "task_name": "Go to pantry",
    "task_type": "navigation",
    "state": "running",
    "target_x": 10.0,
    "target_y": 5.0,
    "target_z": 0.0,
    "start_time_ms": 1774828800000,
    "estimated_completion_ms": 1774828830000,
    "timestamp_ms": 1774828810000,
    "trace_id": "trace_abc123",
    "error_message": ""
}

pnc_status

Planning / control / avoidance summary.

FieldTypeRequiredDescription
statusstringYesrunning / path_block / start_replan / replan_timeout / task_finish / task_error
detailstringNoShort prose summary for cloud dashboards

JSON sample:

json
{
    "status": "path_block",
    "detail": "Static obstacle ahead, replanning"
}

task_status

Task state list. Value type: array<object>.

FieldTypeRequiredDescription
statestringYesrunning / idle / pause, etc.
task_namestringYesHuman-readable task name
task_idstring / numberNoTask ID
task_typestringNoTask type, e.g. navigation / calligraphy
messagestringNoStatus message
updated_at_msnumberNoLast update time

Even with a single task, send an array (length 1). For "no task but sync explicitly", send [{"state":"idle","task_name":""}].

JSON sample:

json
[
    {
        "state": "running",
        "task_name": "Navigate to pantry",
        "task_id": "nav_001",
        "task_type": "navigation",
        "message": "in progress",
        "updated_at_ms": 1774828810000
    }
]

exhibit_tasks

Extension module already used by aimrt_agent for exhibit-tour task lists. Value type: array.

FieldTypeRequiredDescription
task_namestringYesTour task name
task_idstring / numberYesTour task ID
pointsarray<object>NoAssociated points

points element:

FieldTypeRequiredDescription
namestringYesPoint name
idstring / numberYesPoint ID

Updated by whole-array replacement on TE task list change. Business extension, not part of the first-phase navigation recommended set, but actively used in agent.

JSON sample:

json
[
    {
        "task_name": "HQ Tour Route A",
        "task_id": "exhibit_001",
        "points": [
            { "name": "reception", "id": 1 },
            { "name": "showroom", "id": 2 },
            { "name": "pantry", "id": 3 }
        ]
    },
    {
        "task_name": "R&D Tour Route B",
        "task_id": "exhibit_002",
        "points": [
            { "name": "engineering office", "id": 4 },
            { "name": "lab", "id": 5 }
        ]
    }
]

silisoon

Extension module written by the SDK compatibility shim waiter.silisoon.status_upload.

FieldTypeRequiredDescription
modenumberYesBusiness mode: 1=interaction / 2=silisoon / 3=grasp
silisoon_statusnumberYesBusiness state: 1=delivering / 2=idle

JSON sample:

json
{
    "mode": 2,
    "silisoon_status": 1
}

robot_form

Extension module written by the SDK compatibility shim robot.body_info.upload. Value type: string.

Example values: "two-legged" / "four-legged" / "wheeled". The SDK writes the raw form field straight into status_info.robot_form.

JSON sample (bare string):

json
"wheeled"

Note: state_value here is a bare string, not an object. Java reads stateValue directly; Python uses state_value as-is — no json.loads needed.


manipulation

Arm / grasp / manipulation capability status.

FieldTypeRequiredDescription
is_onbooleanYesWhether the manipulation capability is currently enabled / available
modestringNoOptional run mode, e.g. pick / place / assist
updated_at_msnumberNoLast update time

JSON sample:

json
{
    "is_on": true,
    "mode": "pick",
    "updated_at_ms": 1774828810000
}

supported_calligraphy_list

List of supported calligraphy characters. Value type: array<string>.

Useful for exposing capabilities in calligraphy / education / interaction demos. Update by whole-array replacement.

JSON sample:

json
["福", "禄", "寿", "喜", "hello", "happy new year"]

tts_status

TTS runtime snapshot.

FieldTypeRequiredDescription
statestringYesidle / begin / playing / finished / interrupted / failed
item_idstringNoUnique ID of this utterance
event_idstringNoRelated dialog event ID
textstringNoText being spoken
domainstringNoDomain marker — common values:
task_master — system-level utterances, priority l4–l8, rule-based
omnissdk / agent — utterances from the interaction layer (normal dialog / active flows)
prioritynumberNoUtterance priority
error_msgstringNoFailure reason
in_queue_item_idsarray<string>NoRemaining queued utterance IDs
updated_at_msnumberNoLast update time

JSON sample:

json
{
    "state": "playing",
    "item_id": "item_tts_001",
    "event_id": "event_dialog_001",
    "text": "Sure, here is the announcement.",
    "domain": "agent",
    "priority": 5,
    "error_msg": "",
    "in_queue_item_ids": ["item_tts_002", "item_tts_003"],
    "updated_at_ms": 1774828810000
}

vad_status

VAD runtime snapshot.

FieldTypeRequiredDescription
statestringYesbos / continue / eos / bos_timeout / finish
event_idstringNoRelated dialog event ID
updated_at_msnumberNoLast update time

JSON sample:

json
{
    "state": "eos",
    "event_id": "event_dialog_001",
    "updated_at_ms": 1774828810000
}

dialog_status

Dialog status snapshot — covers both before and after quit_voice.

FieldTypeRequiredDescription
statestringYesidle / wake_up / waiting_asr / waiting_dm / processing_local / processing_remote / interrupted / finish
event_idstringNoCurrent dialog event ID
before_quit_voicebooleanNoWhether this is the last stable state before quit_voice
sourcestringNoSource, e.g. sdk_dialog_manager / agent_controller
updated_at_msnumberNoLast update time

quit_voice itself is a control event, not a state module.

JSON sample:

json
{
    "state": "waiting_dm",
    "event_id": "event_dialog_001",
    "before_quit_voice": false,
    "source": "sdk_dialog_manager",
    "updated_at_ms": 1774828810000
}

action_status

Action / skill / running-task snapshot.

FieldTypeRequiredDescription
statestringYesidle / running / pause / completed / failed
tagstringNoAction tag, e.g. write / navigation / grasp
task_namestringNoTask name
task_idstring / numberNoTask ID
messagestringNoStatus message
updated_at_msnumberNoLast update time

Multiple concurrent tasks may be sent as an array.

JSON sample (single task):

json
{
    "state": "running",
    "tag": "write",
    "task_name": "Write 福",
    "task_id": "action_001",
    "message": "stroke 3/5 in progress",
    "updated_at_ms": 1774828810000
}

JSON sample (multi-task array):

json
[
    { "state": "running", "tag": "navigation", "task_name": "Go to A", "task_id": "nav_001", "updated_at_ms": 1774828810000 },
    { "state": "running", "tag": "tts",        "task_name": "Explain A","task_id": "tts_001","updated_at_ms": 1774828810000 }
]

listening_status

On-device listening state.

FieldTypeRequiredDescription
statestringYeslistening / non_wakeup_silent
wakeup_statestringYesalready_wake_up / idle
event_idstringNoCurrent dialog / wake event ID
sourcestringNoSource, e.g. sdk_wakeup_manager
updated_at_msnumberNoLast update time
  • listening — woken up; in listening / interaction state
  • non_wakeup_silent — not woken up; idle or quiet-monitoring

JSON sample:

json
{
    "state": "listening",
    "wakeup_state": "already_wake_up",
    "event_id": "status_nav_001",
    "source": "sdk_wakeup_manager",
    "updated_at_ms": 1774828800210
}

wakeup_behavior_status

Wake-up behaviour override status — reports whether the cloud-sent agentsdk.flow wake-up behaviour override has taken effect.

FieldTypeRequiredDescription
statestringYesactive / expired / cleared / failed
flow_idstringNoFlow that triggered the override
request_event_idstringNoagentsdk.flow.*.request event ID that triggered the override
ttl_msnumberNoKeep-alive duration, default 30000
expires_at_msnumberNoLocal expiry timestamp
interrupt_current_interactionbooleanNoWhether the new wake should interrupt the current interaction
behaviorobjectNoCurrently effective override; same shape as the dispatched setting.wakeup_behavior.behavior. Empty object or omitted = listen-only
error_msgstringNoFailure reason when state=failed
sourcestringNoSource, e.g. agentsdk.flow / agent_controller
updated_at_msnumberNoLast update time

Notes:

  • This module describes the state of the cloud-pushed wake-up behaviour override, not the KWS engine itself.
  • On expiry, send state=expired and restore the default wake-up behaviour.
  • A new override may replace a still-live previous one.
  • behavior={} or omitted behavior means listen-only is in effect.

JSON sample (full behaviour active):

json
{
    "state": "active",
    "flow_id": "flow_mock_demo_001",
    "request_event_id": "event_wakeup_behavior_001",
    "ttl_ms": 30000,
    "expires_at_ms": 1774828830210,
    "interrupt_current_interaction": true,
    "behavior": {
        "tts": { "text": "I'm here, go ahead" },
        "emoticon": { "id": 103, "times": 1 },
        "motion": { "id": [10226] }
    },
    "source": "agentsdk.flow",
    "updated_at_ms": 1774828800210
}

JSON sample (listen-only):

json
{
    "state": "active",
    "flow_id": "flow_mock_demo_001",
    "request_event_id": "event_wakeup_listen_only_001",
    "ttl_ms": 30000,
    "expires_at_ms": 1774828830210,
    "interrupt_current_interaction": false,
    "behavior": {},
    "source": "agentsdk.flow",
    "updated_at_ms": 1774828800210
}

vision_perception

Vision perception summary snapshot, optionally carrying image-relative geometry.

FieldTypeRequiredDescription
perception_modestringNoCurrent mode, e.g. full / only_face
greeting_in_dialogbooleanNoWhether full-duplex greetings are allowed
streamsobjectNoPer-stream summary keyed by stream_id
aggregateobjectYesStable cross-stream aggregated summary
updated_at_msnumberNoLast update time

streams.<stream_id> recommended fields:

FieldTypeRequiredDescription
image_sizeobjectNoOriginal image size {width, height}
person_countnumberYesPersons in this stream
tracked_person_countnumberYesStably-tracked persons in this stream
known_face_idsarray<string>NoRecognised face IDs in this stream
gesture_labelsarray<string>NoGesture labels seen in this stream
action_labelsarray<string>NoAction labels seen in this stream
personsarray<object>NoPer-person geometry + pose details

persons[] recommended fields:

FieldTypeRequiredDescription
track_idnumberNoStable track ID
global_idstringNoCross-cut aggregation ID
face_uidstringNoRecognised face ID
identity_categorystringNorecognized_known / recognized_unknown / unspecified
bodyobjectNoBody box, keypoints, hand/head boxes, body & head pose
faceobjectNoFace box, raw detection box, 5-point landmarks, face pose
gesturesarray<object>NoPer-body gestures + normalised hand ROI
actionsarray<object>NoAction recognition results

aggregate recommended fields:

FieldTypeRequiredDescription
person_countnumberYesPersons currently in view
tracked_person_countnumberYesStably-tracked persons
known_face_idsarray<string>NoRecognised face IDs
unknown_face_countnumberNoVisible but unrecognised faces
facing_face_countnumberNoFaces facing the camera
gesture_labelsarray<string>NoActive gesture labels
action_labelsarray<string>NoActive action labels

Geometry conventions:

  • All *_box_norm / keypoints_norm / landmarks_norm use coordinates normalised to image width and height.
  • Rectangle shape: {x, y, w, h, cx, cy} with components typically in [0, 1].
  • Keypoint shape: {index, x, y, score}.
  • Pose may come from face.yaw/pitch/roll or body.head_pose.

Notes:

  • The module carries normalised detection boxes / keypoints / pose alongside the summary.
  • Capped at 10 Hz by default to avoid flooding the link with high-frequency geometry.
  • Raw images and the full PerceptionAggregateView are not uploaded.
  • For finer-grained instantaneous notifications, pair with the custom event vision.perception.event.

JSON sample:

json
{
    "perception_mode": "full",
    "greeting_in_dialog": true,
    "streams": {
        "front_cam": {
            "image_size": { "width": 1280, "height": 720 },
            "person_count": 2,
            "tracked_person_count": 2,
            "known_face_ids": ["face_uid001"],
            "gesture_labels": ["wave"],
            "action_labels": [],
            "persons": [
                {
                    "track_id": 1,
                    "global_id": "g_1",
                    "face_uid": "face_uid001",
                    "identity_category": "recognized_known",
                    "face": {
                        "box_norm": { "x": 0.29, "y": 0.17, "w": 0.05, "h": 0.11, "cx": 0.32, "cy": 0.22 },
                        "yaw": 4.8, "pitch": -2.1, "roll": 0.9
                    }
                }
            ]
        }
    },
    "aggregate": {
        "person_count": 2,
        "tracked_person_count": 2,
        "known_face_ids": ["face_uid001"],
        "unknown_face_count": 1,
        "facing_face_count": 1,
        "gesture_labels": ["wave"],
        "action_labels": []
    },
    "updated_at_ms": 1774828810000
}

speaker

Current speaker information.

FieldTypeRequiredDescription
event_idstringNoRelated dialog event ID
orderstringNoOrdering for multi-speaker scenarios, e.g. left_to_right
image_sizeobjectNoCurrent-frame image size {width, height}
speaker_idstringNoSpeaker ID (often matches persons[*].face_uid)
doanumberNoDOA estimate
personsarray<object>YesCandidate list

persons[] fields:

FieldTypeRequiredDescription
face_uidstringNoFace ID
identity_categorystringNorecognized_known / recognized_unknown / unspecified
user_namestringNoUser name
user_descstringNoUser description / note
faceobjectNoSame shape as vision_perception.persons[].face
agenumberNoEstimated age
gendernumberNo0 = female / 1 = male

JSON sample:

json
{
    "event_id": "event_xxx",
    "order": "left_to_right",
    "image_size": { "width": 1280, "height": 720 },
    "speaker_id": "face_uid001",
    "doa": 60,
    "persons": [
        {
            "face_uid": "face_uid001",
            "identity_category": "recognized_known",
            "user_name": "Zhang San",
            "user_desc": "Employee #001, founding member",
            "face": {
                "box_norm": { "x": 0.2891, "y": 0.1667, "w": 0.0547, "h": 0.1111, "cx": 0.3164, "cy": 0.2222 },
                "detected_box_norm": { "x": 0.2875, "y": 0.1639, "w": 0.0578, "h": 0.1167, "cx": 0.3164, "cy": 0.2222 },
                "landmarks_norm": [
                    { "index": 0, "x": 0.3023, "y": 0.2056 },
                    { "index": 1, "x": 0.3305, "y": 0.2042 },
                    { "index": 2, "x": 0.3164, "y": 0.2222 }
                ],
                "yaw": 4.8,
                "pitch": -2.1,
                "roll": 0.9,
                "facing_camera": true
            },
            "age": 14,
            "gender": 0
        }
    ]
}

settings

On-device toggle panel — every sub-field is essentially a bool.

FieldTypeRequiredDescription
greeting_in_dialogboolNoWhether full-duplex greetings are enabled

JSON sample:

json
{
    "greeting_in_dialog": true
}

Consuming on the business side

Minimal handler skeletons:

Python

python
import json
from linksoul_agentsdk.passive import Asr2LlmCallback


class MyCb(Asr2LlmCallback):
    def on_state(self, agent_id, event_id, state_name, state_value):
        if state_name == "robot_pose":
            pose = json.loads(state_value)
            print(f"x={pose['x']} y={pose['y']} yaw={pose['yaw']}")
        elif state_name == "tts_status":
            tts = json.loads(state_value)
            if tts["state"] == "finished":
                ...   # TTS finished
        elif state_name == "robot_form":
            # robot_form is a bare string — no json.loads needed
            print(f"form = {state_value}")

    def on_request(self, *a, **k):
        ...

Java

java
import com.alibaba.fastjson2.JSONObject;

@Override
public void onState(String agentId, String eventId, String stateName, String stateValue) {
    switch (stateName) {
        case "robot_pose" -> {
            JSONObject pose = JSONObject.parseObject(stateValue);
            log.info("x={} y={} yaw={}", pose.getDouble("x"), pose.getDouble("y"), pose.getDouble("yaw"));
        }
        case "tts_status" -> {
            JSONObject tts = JSONObject.parseObject(stateValue);
            if ("finished".equals(tts.getString("state"))) {
                // ...
            }
        }
        case "robot_form" -> log.info("form = {}", stateValue);
        default -> { /* ignore */ }
    }
}

Key points:

  1. state_value is not always a JSON objectrobot_form is a bare string, supported_calligraphy_list is array<string>, task_status / exhibit_tasks are array<object>. Check the module spec on this page before parsing.
  2. Frequency-sensitive: robot_pose and vision_perception are capped per module at 10 Hz; don't run heavy I/O inside on_state.
  3. Whole-array replacement: planned_path / exhibit_tasks / supported_calligraphy_list etc. are replaced as full arrays — never merge point-by-point.
  4. Explicit empty state: to signal "no task, but synced", send task_status = [{"state":"idle","task_name":""}].