diff --git a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart index 25ee37dd..f9f5ceda 100644 --- a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart +++ b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart @@ -271,8 +271,8 @@ Future initFlutterBluePlus(InitFlutterBluePlusRef ref) async { for (var element in FlutterBluePlus.connectedDevices) { BaseStatefulDevice? device = knownDevices[element.remoteId.str]; if (device != null) { - device.commandQueue.addCommand(BluetoothMessage(message: "PING", device: device, priority: Priority.low, type: Type.system)); - device.commandQueue.addCommand(BluetoothMessage(message: "BATT", device: device, priority: Priority.low, type: Type.system)); + device.commandQueue.addCommand(BluetoothMessage(message: "PING", device: device, priority: Priority.low, type: CommandType.system)); + device.commandQueue.addCommand(BluetoothMessage(message: "BATT", device: device, priority: Priority.low, type: CommandType.system)); element.readRssi(); } } diff --git a/lib/Backend/Bluetooth/bluetooth_message.dart b/lib/Backend/Bluetooth/bluetooth_message.dart index 097404c4..166fb925 100644 --- a/lib/Backend/Bluetooth/bluetooth_message.dart +++ b/lib/Backend/Bluetooth/bluetooth_message.dart @@ -4,7 +4,7 @@ import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; enum Priority { low, normal, high } -enum Type { system, move, direct } +enum CommandType { system, move, direct } class BluetoothMessage implements Comparable { late final String message; @@ -15,7 +15,7 @@ class BluetoothMessage implements Comparable { Function? onCommandSent; Function(String)? onResponseReceived; double? delay; - Type type; + CommandType type; BluetoothMessage({required this.message, required this.device, required this.priority, required this.type, this.responseMSG, this.onCommandSent, this.onResponseReceived}); diff --git a/lib/Backend/Definitions/Device/device_definition.dart b/lib/Backend/Definitions/Device/device_definition.dart index a0be2be8..8755dde8 100644 --- a/lib/Backend/Definitions/Device/device_definition.dart +++ b/lib/Backend/Definitions/Device/device_definition.dart @@ -148,8 +148,8 @@ class BaseStatefulDevice extends ChangeNotifier { } else if (deviceConnectionState.value == ConnectivityState.connected) { // Add initial commands to the queue Future.delayed(const Duration(seconds: 2), () { - commandQueue.addCommand(BluetoothMessage(message: "VER", device: this, priority: Priority.low, type: Type.system, responseMSG: "VER ")); - commandQueue.addCommand(BluetoothMessage(message: "HWVER", device: this, priority: Priority.low, type: Type.system, responseMSG: "HWVER ")); + commandQueue.addCommand(BluetoothMessage(message: "VER", device: this, priority: Priority.low, type: CommandType.system, responseMSG: "VER ")); + commandQueue.addCommand(BluetoothMessage(message: "HWVER", device: this, priority: Priority.low, type: CommandType.system, responseMSG: "HWVER ")); }); } }); @@ -376,8 +376,8 @@ class CommandQueue { device.deviceState.value = DeviceState.standby; //Without setting state to standby, another command can not run }); // preempt queue - if (bluetoothMessage.type == Type.direct) { - state.toUnorderedList().where((element) => [Type.move, Type.direct].contains(element.type)).forEach((element) => state.remove(element)); + if (bluetoothMessage.type == CommandType.direct) { + state.toUnorderedList().where((element) => [CommandType.move, CommandType.direct].contains(element.type)).forEach((element) => state.remove(element)); } state.add(bluetoothMessage); } diff --git a/lib/Backend/move_lists.dart b/lib/Backend/move_lists.dart index a9429d2a..2eb06e5c 100644 --- a/lib/Backend/move_lists.dart +++ b/lib/Backend/move_lists.dart @@ -111,7 +111,7 @@ class Move { Move(); - Move.move(this.leftServo, this.rightServo, this.speed, this.easingType); + Move.move({this.leftServo = 0, this.rightServo = 0, this.speed = 50, this.easingType = EasingType.linear, this.moveType = MoveType.move}); Move.delay(this.time) { moveType = MoveType.delay; @@ -173,22 +173,24 @@ class MoveLists extends _$MoveLists { return results; } - void add(MoveList moveList) { + Future add(MoveList moveList) async { List state2 = List.from(state); state2.add(moveList); state = state2; + await store(); } - void remove(MoveList moveList) { + Future remove(MoveList moveList) async { List state2 = List.from(state); state2.remove(moveList); state = state2; + await store(); } Future store() async { sequencesLogger.info("Storing sequences"); - HiveProxy.clear('sequences'); - HiveProxy.addAll('sequences', state); + await HiveProxy.clear('sequences'); + await HiveProxy.addAll('sequences', state); } } @@ -201,18 +203,18 @@ Future runAction(BaseAction action, BaseStatefulDevice device) async { Object element = action.commandMoves[i]; if (element is Move) { if (element.moveType == MoveType.delay) { - BluetoothMessage message = BluetoothMessage.delay(delay: element.time, device: device, priority: Priority.normal, type: Type.move); + BluetoothMessage message = BluetoothMessage.delay(delay: element.time, device: device, priority: Priority.normal, type: CommandType.move); device.commandQueue.addCommand(message); } } else if (element is CommandAction) { //Generate move command - BluetoothMessage message = BluetoothMessage(message: element.command, device: device, priority: Priority.normal, type: Type.move, responseMSG: element.response); + BluetoothMessage message = BluetoothMessage(message: element.command, device: device, priority: Priority.normal, type: CommandType.move, responseMSG: element.response); device.commandQueue.addCommand(message); } } } } else if (action is CommandAction) { - device.commandQueue.addCommand(BluetoothMessage(message: action.command, device: device, priority: Priority.normal, responseMSG: action.response, type: Type.move)); + device.commandQueue.addCommand(BluetoothMessage(message: action.command, device: device, priority: Priority.normal, responseMSG: action.response, type: CommandType.move)); plausible.event(name: "Run Action", props: {"Action Name": action.name, "Action Type": action.actionCategory.name}); } else if (action is MoveList) { sequencesLogger.info("Starting MoveList ${action.name}."); @@ -233,8 +235,8 @@ Future runAction(BaseAction action, BaseStatefulDevice device) async { } cmd = "$cmd E${move.easingType.num}F${move.easingType.num}A${move.leftServo.round().clamp(0, 128) ~/ 16}B${move.rightServo.round().clamp(0, 128) ~/ 16}L${move.speed.toInt()}"; } - device.commandQueue.addCommand(BluetoothMessage(message: cmd, device: device, priority: Priority.normal, type: Type.move)); - device.commandQueue.addCommand(BluetoothMessage(message: "TAILU$preset", device: device, priority: Priority.normal, responseMSG: "TAILU$preset END", type: Type.move)); + device.commandQueue.addCommand(BluetoothMessage(message: cmd, device: device, priority: Priority.normal, type: CommandType.move)); + device.commandQueue.addCommand(BluetoothMessage(message: "TAILU$preset", device: device, priority: Priority.normal, responseMSG: "TAILU$preset END", type: CommandType.move)); } else { List newMoveList = List.from(action.moves); //prevent home move from being added to original MoveList if (action.repeat.toInt() > 1) { @@ -246,11 +248,11 @@ Future runAction(BaseAction action, BaseStatefulDevice device) async { for (Move element in newMoveList) { //run move command if (element.moveType == MoveType.delay) { - BluetoothMessage message = BluetoothMessage.delay(delay: element.time, device: device, priority: Priority.normal, type: Type.move); + BluetoothMessage message = BluetoothMessage.delay(delay: element.time, device: device, priority: Priority.normal, type: CommandType.move); device.commandQueue.addCommand(message); } else { //Generate move command - generateMoveCommand(element, device, Type.move).forEach( + generateMoveCommand(element, device, CommandType.move).forEach( (element) { device.commandQueue.addCommand(element); }, @@ -265,7 +267,7 @@ Future runAction(BaseAction action, BaseStatefulDevice device) async { } } -List generateMoveCommand(Move move, BaseStatefulDevice device, Type type) { +List generateMoveCommand(Move move, BaseStatefulDevice device, CommandType type) { List commands = []; if (move.moveType == MoveType.home) { if (device.baseDeviceDefinition.deviceType == DeviceType.ears) { @@ -276,7 +278,7 @@ List generateMoveCommand(Move move, BaseStatefulDevice device, } else if (move.moveType == MoveType.move) { if (device.baseDeviceDefinition.deviceType == DeviceType.ears) { commands.add(BluetoothMessage(message: "SPEED ${move.speed > 60 ? Speed.fast.name.toUpperCase() : Speed.slow.name.toUpperCase()}", device: device, priority: Priority.normal, responseMSG: "SPEED ${move.speed > 60 ? Speed.fast.name.toUpperCase() : Speed.slow.name.toUpperCase()}", type: type)); - commands.add(BluetoothMessage(message: "DSSP ${move.leftServo.round().clamp(0, 128)} ${move.rightServo.round().clamp(0, 128)} 000 000", device: device, priority: Priority.normal, responseMSG: "DSSP END", type: Type.move)); + commands.add(BluetoothMessage(message: "DSSP ${move.leftServo.round().clamp(0, 128)} ${move.rightServo.round().clamp(0, 128)} 000 000", device: device, priority: Priority.normal, responseMSG: "DSSP END", type: CommandType.move)); } else { commands.add(BluetoothMessage( message: "DSSP E${move.easingType.num} F${move.easingType.num} A${move.leftServo.round().clamp(0, 128) ~/ 16} B${move.rightServo.round().clamp(0, 128) ~/ 16} L${move.speed.toInt()} M${move.speed.toInt()}", device: device, priority: Priority.normal, responseMSG: "OK", type: type)); diff --git a/lib/Backend/sensors.dart b/lib/Backend/sensors.dart index 9de0531d..3effd4e1 100644 --- a/lib/Backend/sensors.dart +++ b/lib/Backend/sensors.dart @@ -380,7 +380,7 @@ class EarMicTriggerDefinition extends TriggerDefinition { } rxSubscriptions = []; ref.read(knownDevicesProvider).values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected && element.baseDeviceDefinition.deviceType == DeviceType.ears).forEach((element) { - element.commandQueue.addCommand(BluetoothMessage(message: "ENDLISTEN", device: element, priority: Priority.low, responseMSG: "LISTEN OFF", type: Type.system)); + element.commandQueue.addCommand(BluetoothMessage(message: "ENDLISTEN", device: element, priority: Priority.low, responseMSG: "LISTEN OFF", type: CommandType.system)); }); } @@ -390,7 +390,7 @@ class EarMicTriggerDefinition extends TriggerDefinition { return; } ref.read(knownDevicesProvider).values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected && element.baseDeviceDefinition.deviceType == DeviceType.ears).forEach((element) { - element.commandQueue.addCommand(BluetoothMessage(message: "LISTEN FULL", device: element, priority: Priority.low, type: Type.system)); + element.commandQueue.addCommand(BluetoothMessage(message: "LISTEN FULL", device: element, priority: Priority.low, type: CommandType.system)); }); //add listeners on new device paired deviceRefSubscription = ref.listen(knownDevicesProvider, (previous, next) { @@ -416,7 +416,7 @@ class EarMicTriggerDefinition extends TriggerDefinition { //Store the current streams to keep them open rxSubscriptions = ref.read(knownDevicesProvider).values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected && element.baseDeviceDefinition.deviceType == DeviceType.ears).map( (element) { - element.commandQueue.addCommand(BluetoothMessage(message: "LISTEN FULL", device: element, priority: Priority.low, type: Type.system)); + element.commandQueue.addCommand(BluetoothMessage(message: "LISTEN FULL", device: element, priority: Priority.low, type: CommandType.system)); return element.rxCharacteristicStream.listen( (msg) { if (msg.contains("LISTEN_FULL BANG")) { @@ -459,7 +459,7 @@ class EarTiltTriggerDefinition extends TriggerDefinition { } rxSubscriptions = []; ref.read(knownDevicesProvider).values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected && element.baseDeviceDefinition.deviceType == DeviceType.ears).forEach((element) { - element.commandQueue.addCommand(BluetoothMessage(message: "ENDTILTMODE", device: element, priority: Priority.low, type: Type.system)); + element.commandQueue.addCommand(BluetoothMessage(message: "ENDTILTMODE", device: element, priority: Priority.low, type: CommandType.system)); }); } @@ -469,7 +469,7 @@ class EarTiltTriggerDefinition extends TriggerDefinition { return; } ref.read(knownDevicesProvider).values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected && element.baseDeviceDefinition.deviceType == DeviceType.ears).forEach((element) { - element.commandQueue.addCommand(BluetoothMessage(message: "TILTMODE START", device: element, priority: Priority.low, type: Type.system)); + element.commandQueue.addCommand(BluetoothMessage(message: "TILTMODE START", device: element, priority: Priority.low, type: CommandType.system)); }); //add listeners on new device paired deviceRefSubscription = ref.listen(knownDevicesProvider, (previous, next) { @@ -495,7 +495,7 @@ class EarTiltTriggerDefinition extends TriggerDefinition { //Store the current streams to keep them open rxSubscriptions = ref.read(knownDevicesProvider).values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected && element.baseDeviceDefinition.deviceType == DeviceType.ears).map( (element) { - element.commandQueue.addCommand(BluetoothMessage(message: "TILTMODE START", device: element, priority: Priority.low, type: Type.system)); + element.commandQueue.addCommand(BluetoothMessage(message: "TILTMODE START", device: element, priority: Priority.low, type: CommandType.system)); return element.rxCharacteristicStream.listen( (msg) { if (msg.contains("TILT LEFT")) { diff --git a/lib/Frontend/pages/developer/bluetooth_console.dart b/lib/Frontend/pages/developer/bluetooth_console.dart index 7f3042d8..61405cc9 100644 --- a/lib/Frontend/pages/developer/bluetooth_console.dart +++ b/lib/Frontend/pages/developer/bluetooth_console.dart @@ -37,7 +37,7 @@ class _BluetoothConsoleState extends State { autocorrect: false, onEditingComplete: () { widget.device.commandQueue.addCommand( - BluetoothMessage(message: cmd, device: widget.device, priority: Priority.high, type: Type.system), + BluetoothMessage(message: cmd, device: widget.device, priority: Priority.high, type: CommandType.system), ); setState(() { cmd = ""; @@ -53,7 +53,7 @@ class _BluetoothConsoleState extends State { child: IconButton( onPressed: () { widget.device.commandQueue.addCommand( - BluetoothMessage(message: cmd, device: widget.device, priority: Priority.high, type: Type.system), + BluetoothMessage(message: cmd, device: widget.device, priority: Priority.high, type: CommandType.system), ); setState(() { cmd = ""; diff --git a/lib/Frontend/pages/direct_gear_control.dart b/lib/Frontend/pages/direct_gear_control.dart index a6009b7e..641aa3a9 100644 --- a/lib/Frontend/pages/direct_gear_control.dart +++ b/lib/Frontend/pages/direct_gear_control.dart @@ -59,7 +59,7 @@ class _JoystickState extends ConsumerState { move.moveType = MoveType.home; ref.read(knownDevicesProvider).values.forEach( (element) { - generateMoveCommand(move, element, Type.direct).forEach( + generateMoveCommand(move, element, CommandType.direct).forEach( (message) { message.responseMSG = null; message.priority = Priority.high; @@ -186,7 +186,7 @@ class _JoystickState extends ConsumerState { move.leftServo = left; ref.read(knownDevicesProvider).values.where((element) => deviceTypes.contains(element.baseDeviceDefinition.deviceType)).forEach( (element) { - generateMoveCommand(move, element, Type.direct).forEach( + generateMoveCommand(move, element, CommandType.direct).forEach( (message) { message.responseMSG = null; message.priority = Priority.high; diff --git a/lib/Frontend/pages/shell.dart b/lib/Frontend/pages/shell.dart index 55c06a41..60b6af27 100644 --- a/lib/Frontend/pages/shell.dart +++ b/lib/Frontend/pages/shell.dart @@ -562,7 +562,7 @@ class _ManageGearState extends ConsumerState { TextButton( onPressed: () { setState(() { - widget.device.commandQueue.addCommand(BluetoothMessage(message: "SHUTDOWN", device: widget.device, priority: Priority.high, type: Type.system)); + widget.device.commandQueue.addCommand(BluetoothMessage(message: "SHUTDOWN", device: widget.device, priority: Priority.high, type: CommandType.system)); }); Navigator.pop(context); }, diff --git a/test/Backend/move_lists_test.dart b/test/Backend/move_lists_test.dart new file mode 100644 index 00000000..df35affc --- /dev/null +++ b/test/Backend/move_lists_test.dart @@ -0,0 +1,75 @@ +import 'package:flutter_test/flutter_test.dart' as flTest; +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/src/framework.dart'; +import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; +import 'package:tail_app/Backend/Bluetooth/bluetooth_message.dart'; +import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; +import 'package:tail_app/Backend/move_lists.dart'; + +import '../testing_utils/gear_utils.dart'; +import '../testing_utils/hive_utils.dart'; + +void main() { + setUp(() async { + flTest.TestWidgetsFlutterBinding.ensureInitialized(); + await setupHive(); + }); + tearDown(() async { + await deleteHive(); + }); + group('GenerateMoveCommands', () { + test('Tail DSSP', () async { + ProviderContainer providerContainer = await testGearAdd('MiTail'); + BaseStatefulDevice baseStatefulDevice = providerContainer.read(knownDevicesProvider).values.first; + Move move = Move.move(leftServo: 50, rightServo: 100); + CommandType type = CommandType.move; + List commands = generateMoveCommand(move, baseStatefulDevice, type); + expect(commands.length, 1); + expect(commands.first.message, 'DSSP E0 F0 A3 B6 L50 M50'); + expect(commands.first.responseMSG, 'OK'); + expect(commands.first.device, baseStatefulDevice); + expect(commands.first.type, type); + }); + test('Tail Home', () async { + ProviderContainer providerContainer = await testGearAdd('MiTail'); + BaseStatefulDevice baseStatefulDevice = providerContainer.read(knownDevicesProvider).values.first; + Move move = Move.home(); + CommandType type = CommandType.move; + List commands = generateMoveCommand(move, baseStatefulDevice, type); + expect(commands.length, 1); + expect(commands.first.message, 'TAILHM'); + expect(commands.first.responseMSG, 'END TAILHM'); + expect(commands.first.device, baseStatefulDevice); + expect(commands.first.type, type); + }); + test('EAR DSSP', () async { + ProviderContainer providerContainer = await testGearAdd('EG2'); + BaseStatefulDevice baseStatefulDevice = providerContainer.read(knownDevicesProvider).values.first; + Move move = Move.move(leftServo: 50, rightServo: 100); + CommandType type = CommandType.move; + List commands = generateMoveCommand(move, baseStatefulDevice, type); + expect(commands.length, 2); + expect(commands[0].message, 'SPEED SLOW'); + expect(commands[0].responseMSG, 'SPEED SLOW'); + expect(commands[0].device, baseStatefulDevice); + expect(commands[0].type, type); + + expect(commands[1].message, 'DSSP 50 100 000 000'); + expect(commands[1].responseMSG, 'DSSP END'); + expect(commands[1].device, baseStatefulDevice); + expect(commands[1].type, type); + }); + test('Ear Home', () async { + ProviderContainer providerContainer = await testGearAdd('EG2'); + BaseStatefulDevice baseStatefulDevice = providerContainer.read(knownDevicesProvider).values.first; + Move move = Move.home(); + CommandType type = CommandType.move; + List commands = generateMoveCommand(move, baseStatefulDevice, type); + expect(commands.length, 1); + expect(commands.first.message, 'EARHOME'); + expect(commands.first.responseMSG, 'EARHOME END'); + expect(commands.first.device, baseStatefulDevice); + expect(commands.first.type, type); + }); + }); +}