feat: implement ble tag scanning

This commit is contained in:
2026-05-16 17:42:57 +02:00
parent f37176cce5
commit 568a851d07
6 changed files with 257 additions and 22 deletions
@@ -14,24 +14,72 @@ class PhoenixTagRepository implements TagRepository {
final RealtimeDataClient realtime;
@override
Future<List<Tag>> getTags() => throw UnimplementedError();
Future<List<Tag>> getTags() async {
final list = await tagClient.getTags();
return list.map((j) => Tag.fromJson(j as Map<String, dynamic>)).toList();
}
@override
Future<Tag> getTag(String id) => throw UnimplementedError();
Future<Tag> getTag(int id) async =>
Tag.fromJson(await tagClient.getTag(id));
@override
Future<Tag> createTag({required String id, required String name}) =>
throw UnimplementedError();
Future<Tag> createTag({required String tag_id, required String name}) async =>
Tag.fromJson(await tagClient.createTag({'tag_id': tag_id, 'name': name}));
@override
Future<Tag> updateTag(String id, {String? name}) => throw UnimplementedError();
Future<Tag> updateTag(int id, {String? name}) async {
final params = <String, dynamic>{};
if (name != null) params['name'] = name;
return Tag.fromJson(await tagClient.updateTag(id, params));
}
@override
Future<void> deleteTag(String id) => throw UnimplementedError();
Future<void> deleteTag(int id) => tagClient.deleteTag(id);
@override
Stream<List<TagPosition>> watchPositions() => throw UnimplementedError();
Stream<List<TagPosition>> watchPositions() =>
realtime.channel('tags').map((payload) {
final list = payload['positions'] as List<dynamic>? ?? [];
return list.map((j) {
final m = j as Map<String, dynamic>;
return TagPosition(
tagId: m['tag_id'] as String,
name: m['name'] as String,
color: m['color'] as String,
roomId: m['room_id'] as int?,
);
}).toList();
});
@override
Stream<List<Particle>> watchParticleCloud() => throw UnimplementedError();
Stream<List<Particle>> watchParticleCloud() =>
realtime.channel('localiserd').map((payload) {
final list = payload['particles'] as List<dynamic>? ?? [];
return list
.map((j) => Particle.fromJson(j as Map<String, dynamic>))
.toList();
});
@override
Stream<Map<int, List<String>>> watchRoomOccupancy() async* {
final state = <int, List<String>>{};
// Seed from the HTTP snapshot so the UI isn't blank until the first push.
final snapshot = await tagClient.getOccupancy();
for (final entry in snapshot.cast<Map<String, dynamic>>()) {
final roomId = entry['room_id'] as int?;
if (roomId != null) {
state.putIfAbsent(roomId, () => []).add(entry['tag_id'] as String);
}
}
yield Map<int, List<String>>.from(state);
await for (final payload in realtime.channel('rooms:occupancy')) {
final roomId = payload['room_id'] as int;
final occupants = (payload['occupants'] as List<dynamic>).cast<String>();
state[roomId] = occupants;
yield Map<int, List<String>>.from(state);
}
}
}
+8 -4
View File
@@ -3,14 +3,18 @@ import '../../domain/models/particle.dart';
abstract class TagRepository {
Future<List<Tag>> getTags();
Future<Tag> getTag(String id);
Future<Tag> createTag({required String id, required String name});
Future<Tag> updateTag(String id, {String? name});
Future<void> deleteTag(String id);
Future<Tag> getTag(int id);
Future<Tag> createTag({required String tag_id, required String name});
Future<Tag> updateTag(int id, {String? name});
Future<void> deleteTag(int id);
/// Live stream of all tag positions, pushed by localiserd over Phoenix channel.
Stream<List<TagPosition>> watchPositions();
/// Live stream of particle filter cloud snapshots.
Stream<List<Particle>> watchParticleCloud();
/// Room occupancy delta stream.
/// room_id -> list of tag_id strings currently in that room.
Stream<Map<int, List<String>>> watchRoomOccupancy();
}