Skip to content

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