Rider Workflow - Developer Guide¶
Complete code examples for implementing the rider self-service battery swap workflow via mobile app.
Overview¶
The rider workflow handles mobile app-driven battery swaps at automated swap stations. Customers use their smartphones to discover stations, initiate service, and complete transactions independently.
Prerequisites¶
Before starting the rider workflow, you MUST complete the Pre-Service Setup.
# Phase 0 REQUIRED:
# 1. Odoo Subscription Sync
# 2. Service State Initialization
Why? Without Phase 0:
- ❌ Payment status not synced
- ❌ serviceStates array is empty
- ❌ Service quota tracking unavailable
Complete Rider Journey¶
W1: Asset Discovery → W2: Service Intent → W3: Customer Binding →
W4: Fleet Allocation → W5: Service Completion → Billing
Phase W1: Asset Discovery¶
Asset Discovery¶
Query available swap stations and batteries near the rider's location.
Publish Topic:
call/uxi/service/plan/service-plan-basic-newest-d/get_assets
Payload:
{
"timestamp": "2025-01-15T08:10:00Z",
"plan_id": "service-plan-basic-newest-d",
"correlation_id": "asset-discovery-001",
"actor": {
"type": "customer",
"id": "CUST-RIDER-001"
},
"data": {
"action": "GET_REQUIRED_ASSET_IDS",
"rider_location": {
"lat": -1.2921,
"lng": 36.8219
},
"search_radius": 10
}
}
Subscribe Topic:
rtrn/abs/service/plan/service-plan-basic-newest-d/get_assets
Expected Response:
{
"signals": ["ASSET_IDS_RESOLVED", "LOCATION_CONTEXT_APPLIED", "DEPENDENCIES_MAPPED"],
"metadata": {
"required_fleet_types": ["swap_station_fleet", "battery_fleet_standard"],
"fleet_ids": {
"swap_station_fleet": ["fleet-swap-stations-nairobi"],
"battery_fleet_standard": ["fleet-battery-standard-kenya"]
},
"fleet_dependencies": {
"fleet-swap-stations-nairobi": ["fleet-battery-standard-kenya"]
}
}
}
What You Get: - List of nearby swap stations - Available battery fleets - Fleet dependencies for ARM resolution
Phase W2: Service Intent¶
Declare Service Intent¶
Customer declares intent to visit a swap station.
Publish Topic:
emit/uxi/service/plan/service-plan-basic-newest-d/service_intent
Payload:
{
"timestamp": "2025-01-15T08:20:00Z",
"plan_id": "service-plan-basic-newest-d",
"correlation_id": "service-intent-001",
"actor": {
"type": "customer",
"id": "CUST-RIDER-001"
},
"data": {
"action": "EMIT_SERVICE_INTENT_SIGNAL",
"target_location_id": "LOC001",
"estimated_arrival_time": "2025-01-15T08:35:00Z",
"requested_services": ["battery_swap"]
}
}
Subscribe Topic:
echo/abs/service/plan/service-plan-basic-newest-d/service_intent_result
Expected Response:
{
"signals": ["INTENT_SIGNAL_EMITTED", "LOCATION_VALIDATED", "SERVICE_REQUIREMENTS_RESOLVED"],
"metadata": {
"target_location_id": "LOC001",
"estimated_arrival": "2025-01-15T08:35:00Z",
"requested_services": ["battery_swap"]
}
}
Secondary Emission (automatic - notifies location staff):
- Topic: emit/abs/service_intent/customer-test-rider-001/LOC001
- Purpose: Alert station personnel of incoming customer
FSM Impact:
- service_cycle: Transitions with CONTINUE_SERVICE_REQUESTED
Phase W3: Customer Binding¶
Bind Customer to Location¶
Establish secure service session when customer arrives at station.
Publish Topic:
emit/uxi/service/plan/service-plan-basic-newest-d/customer_binding
Payload:
{
"timestamp": "2025-01-15T08:35:00Z",
"plan_id": "service-plan-basic-newest-d",
"correlation_id": "binding-001",
"actor": {
"type": "customer",
"id": "CUST-RIDER-001"
},
"data": {
"action": "BIND_CUSTOMER_TO_LOCATION",
"location_id": "LOC001",
"requested_services": ["battery_swap"],
"authentication_method": "mobile_app"
}
}
Subscribe Topic:
echo/abs/service/plan/service-plan-basic-newest-d/customer_binding_result
Expected Response:
{
"signals": ["BINDING_ESTABLISHED", "SERVICE_VALIDATED", "LOCATION_ACTIONS_TRIGGERED"],
"metadata": {
"customer_id": "customer-test-rider-001",
"location_id": "LOC001",
"service_account_validation": {
"valid": true,
"allowedServices": ["battery_swap"]
},
"location_actions": {
"triggered_actions": [
{
"action": "NOTIFY_PERSONNEL",
"message": "Customer customer-test-rider-001 requesting battery swap at LOC001"
},
{
"action": "OPEN_CHARGING_BAY",
"bay_id": "LOC001_bay_1",
"unlock_duration": 300
}
]
},
"session_token": "session_customer-test-rider-001_LOC001_1737800701000"
}
}
What Happens: 1. ✅ Service account validated 2. ✅ Session token generated 3. ✅ Charging bay unlocked automatically 4. ✅ Station personnel notified 5. ✅ 5-minute timeout window starts
FSM Impact:
- service_cycle: Transitions with DEPOSIT_CONFIRMED
Phase W4: Fleet Allocation¶
Request Asset Allocation¶
System coordinates with ARM to allocate specific battery.
Publish Topic:
emit/uxi/service/plan/service-plan-basic-newest-d/fleet_allocation
Payload:
{
"timestamp": "2025-01-15T08:37:00Z",
"plan_id": "service-plan-basic-newest-d",
"correlation_id": "allocation-001",
"actor": {
"type": "system",
"id": "service-coordinator"
},
"data": {
"action": "SEND_ASSET_ALLOCATION_SIGNAL",
"target_fleet_id": "fleet-swap-stations-nairobi",
"location_id": "LOC001",
"service_sequence": "battery_swap",
"session_token": "session_customer-test-rider-001_LOC001_1737800701000"
}
}
Subscribe Topic:
echo/abs/service/plan/service-plan-basic-newest-d/fleet_allocation_result
Expected Response:
{
"signals": ["INSTRUCTION_SENT", "FLEET_ALLOCATION_REQUESTED", "ECHO_AWAITING"],
"metadata": {
"fleet_instruction": {
"mqtt_topic": "emit/BSS/fleet_allocation/fleet-swap-stations-nairobi/battery_swap",
"fleet_id": "fleet-swap-stations-nairobi",
"location_context": "LOC001"
},
"arm_delegation": {
"fleet_to_asset_resolution": "ARM will resolve fleet to specific available assets",
"location_based_selection": "ARM will filter by location"
},
"echo_configuration": {
"expected_echo_topic": "echo/BSS/fleet_allocation/fleet-swap-stations-nairobi/completed",
"timeout_seconds": 300
}
}
}
Secondary Emission (automatic - instructs ARM):
- Topic: emit/BSS/fleet_allocation/fleet-swap-stations-nairobi/battery_swap
- Purpose: ARM resolves fleet to specific available battery
- Expected Echo: echo/BSS/fleet_allocation/fleet-swap-stations-nairobi/completed
Phase W5: Service Completion¶
Update Service States¶
Process completed swap and update quotas.
Publish Topic:
emit/abs/service/plan/service-plan-basic-newest-d/service_completion
Payload:
{
"timestamp": "2025-01-15T08:45:00Z",
"plan_id": "service-plan-basic-newest-d",
"correlation_id": "completion-001",
"actor": {
"type": "system",
"id": "asset-management-system"
},
"data": {
"action": "UPDATE_SERVICE_STATES_AND_BILLING",
"old_battery_id": "BAT_001_RETURNED",
"new_battery_id": "BAT_002_ISSUED",
"energy_transferred": 45.5,
"service_duration": 180
}
}
Subscribe Topic:
echo/abs/service/plan/service-plan-basic-newest-d/service_completion_result
Expected Response:
{
"signals": ["ALL_STATES_UPDATED", "BATTERY_STATES_SYNCED", "QUOTAS_UPDATED"],
"metadata": {
"battery_updates": {
"old_battery": {
"battery_id": "BAT_001_RETURNED",
"previous_current_asset": "customer-test-rider-001",
"new_current_asset": "location_inventory"
},
"new_battery": {
"battery_id": "BAT_002_ISSUED",
"previous_current_asset": "location_inventory",
"new_current_asset": "customer-test-rider-001"
}
},
"service_metrics": {
"swap_count_increment": 1,
"energy_transferred": 45.5,
"service_duration": 180,
"cumulative_swaps_today": 1,
"service_cost_calculated": 22.75
},
"quota_analysis": {
"quotas_checked": [
{
"service_id": "svc-battery-fleet-kenya-premium",
"quota_before": 0,
"quota_consumed": 1,
"quota_after": 1,
"quota_limit": 100000000,
"quota_exceeded": false,
"is_infinity": true
}
],
"invoice_generation_required": false
}
}
}
What Happens:
1. ✅ Battery asset assignments updated (old → inventory, new → customer)
2. ✅ Service quotas updated (used += 1)
3. ✅ Service metrics calculated
4. ✅ Billing analysis performed (no invoice if quota not exceeded)
FSM Impact:
- service_cycle: Transitions with BATTERY_RETURNED
Report Usage to Odoo¶
Report completed swap for billing milestone.
Publish Topic:
emit/uxi/billing/plan/service-plan-basic-newest-d/usage_report
Payload:
{
"timestamp": "2025-01-15T08:50:00Z",
"plan_id": "service-plan-basic-newest-d",
"correlation_id": "usage-report-001",
"actor": {
"type": "system",
"id": "asset-management"
},
"data": {
"action": "REPORT_SERVICE_USAGE_TO_ODOO",
"usage_type": "battery_swap_completed",
"service_completion_details": {
"old_battery_id": "BAT_001_RETURNED",
"new_battery_id": "BAT_002_ISSUED",
"energy_transferred": 45.5,
"service_duration": 180
}
}
}
Subscribe Topics:
echo/uxi/billing/plan/service-plan-basic-newest-d/usage_report
echo/odo/billing/plan/+/billing_processed
Secondary Emission (automatic - to Odoo):
- Topic: emit/abs/billing/plan/service-plan-basic-newest-d/swap_completed
- Expected Echo: echo/odo/billing/plan/service-plan-basic-newest-d/billing_processed
Process Odoo Billing Echo¶
Handle billing confirmation from Odoo.
Subscribe Topic:
echo/odo/billing/plan/+/processed
Note: This message comes FROM Odoo (you don't publish it).
Expected Message from Odoo:
{
"timestamp": "2025-01-15T08:51:00Z",
"plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
"correlation_id": "usage-report-001",
"actor": {
"type": "system",
"id": "odoo-billing"
},
"data": {
"action": "PROCESS_ODOO_BILLING_ECHO",
"echo_data": {
"correlation_id": "usage-report-001",
"billing_status": "processed",
"invoice_id": "INV-2025-01-001",
"invoice_amount": 10.00
}
}
}
Complete End-to-End Example¶
Rider Self-Service Flow Script¶
#!/bin/bash
# Complete rider workflow for mobile app-driven battery swap
PLAN_ID="service-plan-basic-newest-d"
CUSTOMER_ID="CUST-RIDER-001"
LOCATION_ID="LOC001"
echo "===== RIDER SELF-SERVICE BATTERY SWAP ====="
echo "Plan: $PLAN_ID"
echo "Customer: $CUSTOMER_ID"
echo ""
# Phase 0: Pre-Service Setup (see odoo-sync-workflow-guide.md)
# (Must be completed first!)
# Phase W1: Asset Discovery
echo "W1.1: Discovering nearby swap stations..."
mosquitto_pub -h localhost -t "call/uxi/service/plan/$PLAN_ID/get_assets" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"flow-001-assets\",
\"actor\": {\"type\": \"customer\", \"id\": \"$CUSTOMER_ID\"},
\"data\": {
\"action\": \"GET_REQUIRED_ASSET_IDS\",
\"rider_location\": {\"lat\": -1.2921, \"lng\": 36.8219},
\"search_radius\": 10
}
}"
sleep 2
echo "W1.2: Checking payment status..."
mosquitto_pub -h localhost -t "call/uxi/billing/plan/$PLAN_ID/payment_check" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"flow-001-payment\",
\"actor\": {\"type\": \"customer\", \"id\": \"$CUSTOMER_ID\"},
\"data\": {
\"action\": \"CHECK_ODOO_PAYMENT_STATUS\",
\"force_real_time_check\": false
}
}"
sleep 2
# Phase W2: Service Intent
echo "W2.1: Declaring service intent..."
mosquitto_pub -h localhost -t "emit/uxi/service/plan/$PLAN_ID/service_intent" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"flow-001-intent\",
\"actor\": {\"type\": \"customer\", \"id\": \"$CUSTOMER_ID\"},
\"data\": {
\"action\": \"EMIT_SERVICE_INTENT_SIGNAL\",
\"target_location_id\": \"$LOCATION_ID\",
\"estimated_arrival_time\": \"$(date -u -d '+15 minutes' +%Y-%m-%dT%H:%M:%SZ)\",
\"requested_services\": [\"battery_swap\"]
}
}"
sleep 2
# Phase W3: Customer Binding
echo "W3.1: Binding customer to location..."
mosquitto_pub -h localhost -t "emit/uxi/service/plan/$PLAN_ID/customer_binding" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"flow-001-binding\",
\"actor\": {\"type\": \"customer\", \"id\": \"$CUSTOMER_ID\"},
\"data\": {
\"action\": \"BIND_CUSTOMER_TO_LOCATION\",
\"location_id\": \"$LOCATION_ID\",
\"requested_services\": [\"battery_swap\"],
\"authentication_method\": \"mobile_app\"
}
}"
sleep 3
# Phase W4: Fleet Allocation
echo "W4.1: Requesting fleet allocation..."
mosquitto_pub -h localhost -t "emit/uxi/service/plan/$PLAN_ID/fleet_allocation" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"flow-001-allocation\",
\"actor\": {\"type\": \"system\", \"id\": \"service-coordinator\"},
\"data\": {
\"action\": \"SEND_ASSET_ALLOCATION_SIGNAL\",
\"target_fleet_id\": \"fleet-swap-stations-nairobi\",
\"location_id\": \"$LOCATION_ID\",
\"service_sequence\": \"battery_swap\"
}
}"
sleep 3
# Phase W5: Service Completion
echo "W5.1: Processing service completion..."
mosquitto_pub -h localhost -t "emit/abs/service/plan/$PLAN_ID/service_completion" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"flow-001-completion\",
\"actor\": {\"type\": \"system\", \"id\": \"asset-management-system\"},
\"data\": {
\"action\": \"UPDATE_SERVICE_STATES_AND_BILLING\",
\"old_battery_id\": \"BAT_001_RETURNED\",
\"new_battery_id\": \"BAT_002_ISSUED\",
\"energy_transferred\": 45.5,
\"service_duration\": 180
}
}"
sleep 2
echo "W5.2: Reporting usage to Odoo..."
mosquitto_pub -h localhost -t "emit/uxi/billing/plan/$PLAN_ID/usage_report" -m "{
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"plan_id\": \"$PLAN_ID\",
\"correlation_id\": \"flow-001-billing\",
\"actor\": {\"type\": \"system\", \"id\": \"asset-management\"},
\"data\": {
\"action\": \"REPORT_SERVICE_USAGE_TO_ODOO\",
\"usage_type\": \"battery_swap_completed\",
\"service_completion_details\": {
\"old_battery_id\": \"BAT_001_RETURNED\",
\"new_battery_id\": \"BAT_002_ISSUED\",
\"energy_transferred\": 45.5,
\"service_duration\": 180
}
}
}"
echo ""
echo "✅ Rider workflow completed!"
echo "Customer has successfully swapped battery"
echo "Billing report sent to Odoo for invoicing"
Monitoring¶
Subscribe to all rider workflow topics:
Terminal 1: UXI messages (customer actions)
emit/uxi/service/#
echo/abs/service/#
call/uxi/service/#
rtrn/abs/service/#
Terminal 2: Billing messages
emit/uxi/billing/#
echo/uxi/billing/#
echo/odo/billing/#
Terminal 3: Fleet allocation (ARM coordination)
emit/BSS/fleet_allocation/#
echo/BSS/fleet_allocation/#
Terminal 4: Service intent (location notifications)
emit/abs/service_intent/#
Mobile App Integration¶
React Native Example¶
import mqtt from 'mqtt';
class RiderWorkflowService {
private client: mqtt.MqttClient;
private planId: string;
constructor(planId: string) {
this.planId = planId;
this.client = mqtt.connect('mqtt://your-broker:1883');
}
// W1: Discover nearby stations
async discoverStations(latitude: number, longitude: number) {
const topic = `call/uxi/service/plan/${this.planId}/get_assets`;
const payload = {
timestamp: new Date().toISOString(),
plan_id: this.planId,
correlation_id: `discovery-${Date.now()}`,
actor: { type: 'customer', id: 'CURRENT_USER_ID' },
data: {
action: 'GET_REQUIRED_ASSET_IDS',
rider_location: { lat: latitude, lng: longitude },
search_radius: 10
}
};
return new Promise((resolve) => {
// Subscribe to response
this.client.subscribe(`rtrn/abs/service/plan/${this.planId}/get_assets`);
this.client.on('message', (topic, message) => {
if (topic.includes('get_assets')) {
resolve(JSON.parse(message.toString()));
}
});
// Publish request
this.client.publish(topic, JSON.stringify(payload));
});
}
// W2: Declare service intent
async declareIntent(locationId: string) {
const topic = `emit/uxi/service/plan/${this.planId}/service_intent`;
const payload = {
timestamp: new Date().toISOString(),
plan_id: this.planId,
correlation_id: `intent-${Date.now()}`,
actor: { type: 'customer', id: 'CURRENT_USER_ID' },
data: {
action: 'EMIT_SERVICE_INTENT_SIGNAL',
target_location_id: locationId,
estimated_arrival_time: new Date(Date.now() + 15 * 60000).toISOString(),
requested_services: ['battery_swap']
}
};
this.client.publish(topic, JSON.stringify(payload));
}
// W3: Bind to location (on arrival)
async bindToLocation(locationId: string) {
const topic = `emit/uxi/service/plan/${this.planId}/customer_binding`;
const payload = {
timestamp: new Date().toISOString(),
plan_id: this.planId,
correlation_id: `binding-${Date.now()}`,
actor: { type: 'customer', id: 'CURRENT_USER_ID' },
data: {
action: 'BIND_CUSTOMER_TO_LOCATION',
location_id: locationId,
requested_services: ['battery_swap'],
authentication_method: 'mobile_app'
}
};
return new Promise((resolve) => {
this.client.subscribe(`echo/abs/service/plan/${this.planId}/customer_binding_result`);
this.client.on('message', (topic, message) => {
if (topic.includes('customer_binding_result')) {
resolve(JSON.parse(message.toString()));
}
});
this.client.publish(topic, JSON.stringify(payload));
});
}
}
Common Errors¶
| Error Signal | Cause | Solution |
|---|---|---|
LOCATION_INVALID |
Location not found | Verify location ID exists |
PAYMENT_OVERDUE |
Payment not current | Update payment in Odoo |
QUOTA_EXHAUSTED |
No swaps remaining | Process quota top-up |
SERVICE_PLAN_INVALID |
Plan not active | Complete Phase 0 first |
ACCOUNT_RESTRICTED |
Service blocked | Contact support |
Next Steps¶
- Attendant Workflow Guide - Staff-operated swaps
- Service State Management - Quota tracking
- Odoo Sync Workflow - Payment integration