enum ScooterConnectionStatus { disconnected, connecting, connected, authenticating, authenticated, ready, error, } /// Observable snapshot of everything the UI needs. /// /// Numeric protocol values are kept in their native representation. Units /// (km vs miles, Ah vs mAh, percent) are NOT asserted here until they have /// been LIVE VERIFIED against a physical scooter. class ScooterState { final ScooterConnectionStatus connectionStatus; final bool authenticated; final bool canWrite; /// Human-readable description of the last error, if [connectionStatus] is /// [ScooterConnectionStatus.error]. final String? errorMessage; /// Which connection step is in progress (0-based index into /// [connectionSteps]) while [connectionStatus] is connecting or /// authenticated-but-waiting. Null when no multi-step work is running. final int? connectionStep; /// Steps shown to the rider while a link is being established. static const connectionSteps = [ 'Connecting', 'Setting up services', 'Checking this is a supported device', 'Waiting for telemetry', 'Waiting for scooter confirmation', ]; final double? speed; final double? voltage; final double? current; final double? power; final double? tripDistance; final double? odometer; final int? batteryLevel; final int? batteryTemperature; final int? batteryCycles; final int? motorTemperature; final int? controllerTemperature; final int? gear; final bool? locked; final bool? headlight; final bool? atmosphereLight; final bool? cruiseControl; final bool? leftTurnSignal; final bool? rightTurnSignal; /// Scooter's own unit preference bit (monitor byte 22 bit 5). final bool? imperial; /// Speed limit for the current gear and the highest configured limit, in /// native protocol units. Gear-to-mode mapping is INFERRED. final int? speedLimit; final int? maxSpeedLimit; final String? displayId; final String? displayVersion; const ScooterState({ this.connectionStatus = ScooterConnectionStatus.disconnected, this.authenticated = false, this.canWrite = false, this.errorMessage, this.connectionStep, this.speed, this.voltage, this.current, this.power, this.tripDistance, this.odometer, this.batteryLevel, this.batteryTemperature, this.batteryCycles, this.motorTemperature, this.controllerTemperature, this.gear, this.locked, this.headlight, this.atmosphereLight, this.cruiseControl, this.leftTurnSignal, this.rightTurnSignal, this.imperial, this.speedLimit, this.maxSpeedLimit, this.displayId, this.displayVersion, }); /// Sentinel so callers can explicitly clear a nullable field. static const Object _unset = Object(); ScooterState copyWith({ ScooterConnectionStatus? connectionStatus, bool? authenticated, bool? canWrite, Object? errorMessage = _unset, Object? connectionStep = _unset, Object? speed = _unset, Object? voltage = _unset, Object? current = _unset, Object? power = _unset, Object? tripDistance = _unset, Object? odometer = _unset, Object? batteryLevel = _unset, Object? batteryTemperature = _unset, Object? batteryCycles = _unset, Object? motorTemperature = _unset, Object? controllerTemperature = _unset, Object? gear = _unset, Object? locked = _unset, Object? headlight = _unset, Object? atmosphereLight = _unset, Object? cruiseControl = _unset, Object? leftTurnSignal = _unset, Object? rightTurnSignal = _unset, Object? imperial = _unset, Object? speedLimit = _unset, Object? maxSpeedLimit = _unset, Object? displayId = _unset, Object? displayVersion = _unset, }) { return ScooterState( connectionStatus: connectionStatus ?? this.connectionStatus, authenticated: authenticated ?? this.authenticated, canWrite: canWrite ?? this.canWrite, errorMessage: errorMessage == _unset ? this.errorMessage : errorMessage as String?, connectionStep: connectionStep == _unset ? this.connectionStep : connectionStep as int?, speed: speed == _unset ? this.speed : speed as double?, voltage: voltage == _unset ? this.voltage : voltage as double?, current: current == _unset ? this.current : current as double?, power: power == _unset ? this.power : power as double?, tripDistance: tripDistance == _unset ? this.tripDistance : tripDistance as double?, odometer: odometer == _unset ? this.odometer : odometer as double?, batteryLevel: batteryLevel == _unset ? this.batteryLevel : batteryLevel as int?, batteryTemperature: batteryTemperature == _unset ? this.batteryTemperature : batteryTemperature as int?, batteryCycles: batteryCycles == _unset ? this.batteryCycles : batteryCycles as int?, motorTemperature: motorTemperature == _unset ? this.motorTemperature : motorTemperature as int?, controllerTemperature: controllerTemperature == _unset ? this.controllerTemperature : controllerTemperature as int?, gear: gear == _unset ? this.gear : gear as int?, locked: locked == _unset ? this.locked : locked as bool?, headlight: headlight == _unset ? this.headlight : headlight as bool?, atmosphereLight: atmosphereLight == _unset ? this.atmosphereLight : atmosphereLight as bool?, cruiseControl: cruiseControl == _unset ? this.cruiseControl : cruiseControl as bool?, leftTurnSignal: leftTurnSignal == _unset ? this.leftTurnSignal : leftTurnSignal as bool?, rightTurnSignal: rightTurnSignal == _unset ? this.rightTurnSignal : rightTurnSignal as bool?, imperial: imperial == _unset ? this.imperial : imperial as bool?, speedLimit: speedLimit == _unset ? this.speedLimit : speedLimit as int?, maxSpeedLimit: maxSpeedLimit == _unset ? this.maxSpeedLimit : maxSpeedLimit as int?, displayId: displayId == _unset ? this.displayId : displayId as String?, displayVersion: displayVersion == _unset ? this.displayVersion : displayVersion as String?, ); } }