import 'dart:convert'; import 'package:bus_infotainment/auth/api_constants.dart'; import 'package:bus_infotainment/backend/live_information.dart'; import 'package:bus_infotainment/tfl_datasets.dart'; import 'package:bus_infotainment/utils/audio%20wrapper.dart'; import 'package:bus_infotainment/utils/delegates.dart'; import 'package:appwrite/appwrite.dart' as appwrite; import 'package:appwrite/models.dart' as models; import 'package:uuid/uuid.dart'; import '../../auth/auth_api.dart'; import 'info_module.dart'; class CommandModule extends InfoModule { final String sessionID; late final String clientID; List _commandHistory = []; get commandHistory => _commandHistory; EventDelegate onCommandReceived = EventDelegate(); CommandModule(this.sessionID){ // generate a random client ID var uuid = Uuid(); clientID = uuid.v4(); if (liveInformation.auth.isAuthenticated()){ print("Auth is authenticated"); _setupListener(); } else { print("Auth is not authenticated"); liveInformation.auth.onLogin.addListener((value) { _setupListener(); }); } } // Will execute the command an event which is triggered when a response is received Future executeCommand(String command) async { EventDelegate delegate = EventDelegate(); final client = liveInformation.auth.client; final databases = appwrite.Databases(client); if (liveInformation.auth.status == AuthStatus.AUTHENTICATED) { final document = await databases.createDocument( databaseId: ApiConstants.INFO_Q_DATABASE_ID, collectionId: ApiConstants.COMMANDS_COLLECTION_ID, documentId: appwrite.ID.unique(), data: { "session_id": sessionID, "command": command, "client_id": clientID, } ); } _onCommandReceived(CommandInfo(command, clientID)); return delegate; } Future _onCommandReceived(CommandInfo commandInfo) async { commandHistory.add(commandInfo); onCommandReceived.trigger(commandInfo); print("Received command: ${commandInfo.command}"); List commandParts = splitCommand(commandInfo.command); String command = commandParts[0]; List args = commandParts.sublist(1); if (command == "Response:") { } else if (command == "announce") { final displayText = args[1]; if (args[0] == "manual") { // announce manual ... List audioFileNames = args.sublist(2); try { if (int.parse(audioFileNames.last) != null) { audioFileNames.removeLast(); } } catch (e) {} DateTime scheduledTime = LiveInformation().syncedTimeModule.Now().add(Duration(seconds: 1)); try { if (int.parse(args.last) != null) { scheduledTime = DateTime.fromMillisecondsSinceEpoch(int.parse(args.last)); } } catch (e) {} liveInformation.announcementModule.queueAnnounceByAudioName( displayText: displayText, audioNames: audioFileNames, scheduledTime: scheduledTime, sendToServer: false ); } else if (args[0] == "info") { int InfoIndex = int.parse(args[1]); DateTime scheduledTime = LiveInformation().syncedTimeModule.Now(); try { if (int.parse(args.last) != null) { scheduledTime = DateTime.fromMillisecondsSinceEpoch(int.parse(args.last)); } } catch (e) {} liveInformation.announcementModule.queueAnnounementByInfoIndex( infoIndex: InfoIndex, scheduledTime: scheduledTime, sendToServer: false ); } else if (args[0].startsWith("dest")) { // announce destination print("Checkpoint 1"); String routeNumber = args[1]; int routeVariantIndex = int.parse(args[2]); DateTime scheduledTime = LiveInformation().syncedTimeModule.Now(); try { if (int.parse(args.last) != null) { scheduledTime = DateTime.fromMillisecondsSinceEpoch(int.parse(args.last)); } } catch (e) {} print("Checkpoint 2"); BusRoute route = LiveInformation().busSequences.routes[routeNumber]!; BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex]; print("Checkpoint 3"); liveInformation.announcementModule.queueAnnouncementByRouteVariant( routeVariant: routeVariant, scheduledTime: scheduledTime, sendToServer: false ); } } else if (command == "setroute") { // setroute LiveInformation liveInformation = LiveInformation(); String routeNumber = args[0]; int routeVariantIndex = int.parse(args[1]); BusRoute route = liveInformation.busSequences.routes[routeNumber]!; BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex]; liveInformation.setRouteVariant_Internal( routeVariant ); executeCommand("Response: v \"Client $clientID set its route to ($routeNumber to ${routeVariant.busStops.last.formattedStopName})\""); } } appwrite.RealtimeSubscription? _subscription; Future _setupListener() async { if (_subscription != null) { return; } final realtime = appwrite.Realtime(LiveInformation().auth.client); _subscription = realtime.subscribe( ['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.COMMANDS_COLLECTION_ID}.documents'] ); _subscription!.stream.listen((event) { print(jsonEncode(event.payload)); // Only do something if the document was created or updated if (!(event.events.first.contains("create") || event.events.first.contains("update"))) { return; } final commandInfo = CommandInfo(event.payload['command'], event.payload['client_id']); if (commandInfo.clientID != clientID) { _onCommandReceived(commandInfo); } }); print("Listening for commands"); await Future.delayed(Duration(seconds: 90)); await _subscription!.close(); _subscription = null; _setupListener(); } } class CommandInfo { final String command; final String clientID; CommandInfo(this.command, this.clientID); } List splitCommand(String command) { var regex = RegExp(r'([^\s"]+)|"([^"]*)"'); var matches = regex.allMatches(command); return matches.map((match) => match.group(0)!.replaceAll('"', '')).toList(); }