diff --git a/Makefile b/Makefile index 85f1847..0da0e7b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ proto: - protoc -I protobuf/ --dart_out=lib/pb protobuf/stillbox.proto google/protobuf/timestamp.proto + protoc -I protobuf/ --dart_out=lib/pb protobuf/stillbox.proto google/protobuf/timestamp.proto google/protobuf/struct.proto patch: dart run patch_package apply diff --git a/lib/controller/play.dart b/lib/controller/play.dart index ace8f15..3977934 100644 --- a/lib/controller/play.dart +++ b/lib/controller/play.dart @@ -10,6 +10,7 @@ import '../pb/stillbox.pb.dart'; abstract class AudioDriver { Future play(Call call); + Stream get playerStateStream; } class Player { @@ -47,6 +48,10 @@ class JustAudioDriver implements AudioDriver { initializer.audioInit(); } + Stream get playerStateStream { + return player.playerStateStream; + } + @override Future play(Call call) async { player.setAudioSource(CallBytesSource(call)); diff --git a/lib/controller/stillbox.dart b/lib/controller/stillbox.dart index aee9727..cb05962 100644 --- a/lib/controller/stillbox.dart +++ b/lib/controller/stillbox.dart @@ -1,22 +1,26 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; +import 'package:just_audio/just_audio.dart'; import '../pb/stillbox.pb.dart'; import 'play.dart'; import 'stillbox_none.dart' if (dart.library.io) 'stillbox_io.dart' if (dart.library.html) 'stillbox_web.dart'; +import 'talkgroups.dart'; + class BadAuthException implements Exception {} class Stillbox extends ChangeNotifier { final storage = Storer(); Player player = Player(); final channel = Socketer(); + late TalkgroupCache tgCache; bool connected = false; late Uri _wsUri; LiveState _state = LiveState.LS_LIVE; Filter? currentFilter; - Call? _currentCall; + SBCall? _currentCall; Uri? baseUri = Uri.base; set state(LiveState newState) { @@ -29,14 +33,23 @@ class Stillbox extends ChangeNotifier { return _state; } - Call? get currentCall => _currentCall; - set currentCall(Call? call) { + SBCall? get currentCall => _currentCall; + set currentCall(SBCall? call) { _currentCall = call; + if (_currentCall != null) { + player.play(_currentCall!.call); + } notifyListeners(); } Stillbox() { setUris(); + player.driver.playerStateStream.listen((event) async { + if ((!event.playing && _currentCall != null) && + event.processingState == ProcessingState.completed) { + currentCall = null; + } + }); } void setUris() { @@ -92,6 +105,7 @@ class Stillbox extends ChangeNotifier { print(error); }); connected = true; + tgCache = TalkgroupCache(channel); notifyListeners(); } @@ -113,11 +127,21 @@ class Stillbox extends ChangeNotifier { final msg = Message.fromBuffer(event); switch (msg.whichToClientMessage()) { case Message_ToClientMessage.call: - player.play(msg.call); + currentCall = SBCall( + msg.call, tgCache.getTg(msg.call.system, msg.call.talkgroup)); case Message_ToClientMessage.notification: case Message_ToClientMessage.popup: case Message_ToClientMessage.error: + case Message_ToClientMessage.tgInfo: + tgCache.handleTgInfo(msg.tgInfo); default: } } } + +class SBCall { + Call call; + Future tg; + + SBCall(this.call, this.tg); +} diff --git a/lib/controller/talkgroups.dart b/lib/controller/talkgroups.dart new file mode 100644 index 0000000..1677abb --- /dev/null +++ b/lib/controller/talkgroups.dart @@ -0,0 +1,31 @@ +import 'dart:async'; +import 'stillbox_none.dart' + if (dart.library.io) 'stillbox_io.dart' + if (dart.library.html) 'stillbox_web.dart'; + +import '../pb/stillbox.pb.dart'; + +class TalkgroupCache { + Socketer socketer; + final StreamController tgiSt = + StreamController.broadcast(); + Map cache = {}; + + TalkgroupCache(this.socketer); + + void handleTgInfo(TalkgroupInfo tgi) { + cache[tgi.tg] = tgi; + tgiSt.add(tgi); + } + + Future getTg(int system, int tgid) async { + final tg = Talkgroup(system: system, talkgroup: tgid); + if (cache[tg] != null) { + return cache[tg]!; + } + + socketer.channel.sink.add(Command(tgCommand: tg).writeToBuffer()); + return tgiSt.stream + .firstWhere((td) => td.tg.system == system && td.tg.talkgroup == tgid); + } +} diff --git a/lib/pb/google/protobuf/struct.pb.dart b/lib/pb/google/protobuf/struct.pb.dart new file mode 100644 index 0000000..847ddda --- /dev/null +++ b/lib/pb/google/protobuf/struct.pb.dart @@ -0,0 +1,283 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/struct.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/src/protobuf/mixins/well_known.dart' as $mixin; + +import 'struct.pbenum.dart'; + +export 'struct.pbenum.dart'; + +/// `Struct` represents a structured data value, consisting of fields +/// which map to dynamically typed values. In some languages, `Struct` +/// might be supported by a native representation. For example, in +/// scripting languages like JS a struct is represented as an +/// object. The details of that representation are described together +/// with the proto support for the language. +/// +/// The JSON representation for `Struct` is JSON object. +class Struct extends $pb.GeneratedMessage with $mixin.StructMixin { + factory Struct({ + $core.Map<$core.String, Value>? fields, + }) { + final $result = create(); + if (fields != null) { + $result.fields.addAll(fields); + } + return $result; + } + Struct._() : super(); + factory Struct.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Struct.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Struct', package: const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), createEmptyInstance: create, toProto3Json: $mixin.StructMixin.toProto3JsonHelper, fromProto3Json: $mixin.StructMixin.fromProto3JsonHelper) + ..m<$core.String, Value>(1, _omitFieldNames ? '' : 'fields', entryClassName: 'Struct.FieldsEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: Value.create, valueDefaultOrMaker: Value.getDefault, packageName: const $pb.PackageName('google.protobuf')) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Struct clone() => Struct()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Struct copyWith(void Function(Struct) updates) => super.copyWith((message) => updates(message as Struct)) as Struct; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Struct create() => Struct._(); + Struct createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Struct getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Struct? _defaultInstance; + + /// Unordered map of dynamically typed values. + @$pb.TagNumber(1) + $core.Map<$core.String, Value> get fields => $_getMap(0); +} + +enum Value_Kind { + nullValue, + numberValue, + stringValue, + boolValue, + structValue, + listValue, + notSet +} + +/// `Value` represents a dynamically typed value which can be either +/// null, a number, a string, a boolean, a recursive struct value, or a +/// list of values. A producer of value is expected to set one of these +/// variants. Absence of any variant indicates an error. +/// +/// The JSON representation for `Value` is JSON value. +class Value extends $pb.GeneratedMessage with $mixin.ValueMixin { + factory Value({ + NullValue? nullValue, + $core.double? numberValue, + $core.String? stringValue, + $core.bool? boolValue, + Struct? structValue, + ListValue? listValue, + }) { + final $result = create(); + if (nullValue != null) { + $result.nullValue = nullValue; + } + if (numberValue != null) { + $result.numberValue = numberValue; + } + if (stringValue != null) { + $result.stringValue = stringValue; + } + if (boolValue != null) { + $result.boolValue = boolValue; + } + if (structValue != null) { + $result.structValue = structValue; + } + if (listValue != null) { + $result.listValue = listValue; + } + return $result; + } + Value._() : super(); + factory Value.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Value.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static const $core.Map<$core.int, Value_Kind> _Value_KindByTag = { + 1 : Value_Kind.nullValue, + 2 : Value_Kind.numberValue, + 3 : Value_Kind.stringValue, + 4 : Value_Kind.boolValue, + 5 : Value_Kind.structValue, + 6 : Value_Kind.listValue, + 0 : Value_Kind.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Value', package: const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), createEmptyInstance: create, toProto3Json: $mixin.ValueMixin.toProto3JsonHelper, fromProto3Json: $mixin.ValueMixin.fromProto3JsonHelper) + ..oo(0, [1, 2, 3, 4, 5, 6]) + ..e(1, _omitFieldNames ? '' : 'nullValue', $pb.PbFieldType.OE, defaultOrMaker: NullValue.NULL_VALUE, valueOf: NullValue.valueOf, enumValues: NullValue.values) + ..a<$core.double>(2, _omitFieldNames ? '' : 'numberValue', $pb.PbFieldType.OD) + ..aOS(3, _omitFieldNames ? '' : 'stringValue') + ..aOB(4, _omitFieldNames ? '' : 'boolValue') + ..aOM(5, _omitFieldNames ? '' : 'structValue', subBuilder: Struct.create) + ..aOM(6, _omitFieldNames ? '' : 'listValue', subBuilder: ListValue.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Value clone() => Value()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Value copyWith(void Function(Value) updates) => super.copyWith((message) => updates(message as Value)) as Value; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Value create() => Value._(); + Value createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Value getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Value? _defaultInstance; + + Value_Kind whichKind() => _Value_KindByTag[$_whichOneof(0)]!; + void clearKind() => clearField($_whichOneof(0)); + + /// Represents a null value. + @$pb.TagNumber(1) + NullValue get nullValue => $_getN(0); + @$pb.TagNumber(1) + set nullValue(NullValue v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasNullValue() => $_has(0); + @$pb.TagNumber(1) + void clearNullValue() => clearField(1); + + /// Represents a double value. + @$pb.TagNumber(2) + $core.double get numberValue => $_getN(1); + @$pb.TagNumber(2) + set numberValue($core.double v) { $_setDouble(1, v); } + @$pb.TagNumber(2) + $core.bool hasNumberValue() => $_has(1); + @$pb.TagNumber(2) + void clearNumberValue() => clearField(2); + + /// Represents a string value. + @$pb.TagNumber(3) + $core.String get stringValue => $_getSZ(2); + @$pb.TagNumber(3) + set stringValue($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasStringValue() => $_has(2); + @$pb.TagNumber(3) + void clearStringValue() => clearField(3); + + /// Represents a boolean value. + @$pb.TagNumber(4) + $core.bool get boolValue => $_getBF(3); + @$pb.TagNumber(4) + set boolValue($core.bool v) { $_setBool(3, v); } + @$pb.TagNumber(4) + $core.bool hasBoolValue() => $_has(3); + @$pb.TagNumber(4) + void clearBoolValue() => clearField(4); + + /// Represents a structured value. + @$pb.TagNumber(5) + Struct get structValue => $_getN(4); + @$pb.TagNumber(5) + set structValue(Struct v) { setField(5, v); } + @$pb.TagNumber(5) + $core.bool hasStructValue() => $_has(4); + @$pb.TagNumber(5) + void clearStructValue() => clearField(5); + @$pb.TagNumber(5) + Struct ensureStructValue() => $_ensure(4); + + /// Represents a repeated `Value`. + @$pb.TagNumber(6) + ListValue get listValue => $_getN(5); + @$pb.TagNumber(6) + set listValue(ListValue v) { setField(6, v); } + @$pb.TagNumber(6) + $core.bool hasListValue() => $_has(5); + @$pb.TagNumber(6) + void clearListValue() => clearField(6); + @$pb.TagNumber(6) + ListValue ensureListValue() => $_ensure(5); +} + +/// `ListValue` is a wrapper around a repeated field of values. +/// +/// The JSON representation for `ListValue` is JSON array. +class ListValue extends $pb.GeneratedMessage with $mixin.ListValueMixin { + factory ListValue({ + $core.Iterable? values, + }) { + final $result = create(); + if (values != null) { + $result.values.addAll(values); + } + return $result; + } + ListValue._() : super(); + factory ListValue.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ListValue.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ListValue', package: const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), createEmptyInstance: create, toProto3Json: $mixin.ListValueMixin.toProto3JsonHelper, fromProto3Json: $mixin.ListValueMixin.fromProto3JsonHelper) + ..pc(1, _omitFieldNames ? '' : 'values', $pb.PbFieldType.PM, subBuilder: Value.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ListValue clone() => ListValue()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ListValue copyWith(void Function(ListValue) updates) => super.copyWith((message) => updates(message as ListValue)) as ListValue; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ListValue create() => ListValue._(); + ListValue createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ListValue getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ListValue? _defaultInstance; + + /// Repeated field of dynamically typed values. + @$pb.TagNumber(1) + $core.List get values => $_getList(0); +} + + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/pb/google/protobuf/struct.pbenum.dart b/lib/pb/google/protobuf/struct.pbenum.dart new file mode 100644 index 0000000..bf7c571 --- /dev/null +++ b/lib/pb/google/protobuf/struct.pbenum.dart @@ -0,0 +1,34 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/struct.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +/// `NullValue` is a singleton enumeration to represent the null value for the +/// `Value` type union. +/// +/// The JSON representation for `NullValue` is JSON `null`. +class NullValue extends $pb.ProtobufEnum { + static const NullValue NULL_VALUE = NullValue._(0, _omitEnumNames ? '' : 'NULL_VALUE'); + + static const $core.List values = [ + NULL_VALUE, + ]; + + static final $core.Map<$core.int, NullValue> _byValue = $pb.ProtobufEnum.initByValue(values); + static NullValue? valueOf($core.int value) => _byValue[value]; + + const NullValue._($core.int v, $core.String n) : super(v, n); +} + + +const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/pb/google/protobuf/struct.pbjson.dart b/lib/pb/google/protobuf/struct.pbjson.dart new file mode 100644 index 0000000..ffa25fa --- /dev/null +++ b/lib/pb/google/protobuf/struct.pbjson.dart @@ -0,0 +1,90 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/struct.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use nullValueDescriptor instead') +const NullValue$json = { + '1': 'NullValue', + '2': [ + {'1': 'NULL_VALUE', '2': 0}, + ], +}; + +/// Descriptor for `NullValue`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List nullValueDescriptor = $convert.base64Decode( + 'CglOdWxsVmFsdWUSDgoKTlVMTF9WQUxVRRAA'); + +@$core.Deprecated('Use structDescriptor instead') +const Struct$json = { + '1': 'Struct', + '2': [ + {'1': 'fields', '3': 1, '4': 3, '5': 11, '6': '.google.protobuf.Struct.FieldsEntry', '10': 'fields'}, + ], + '3': [Struct_FieldsEntry$json], +}; + +@$core.Deprecated('Use structDescriptor instead') +const Struct_FieldsEntry$json = { + '1': 'FieldsEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.google.protobuf.Value', '10': 'value'}, + ], + '7': {'7': true}, +}; + +/// Descriptor for `Struct`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List structDescriptor = $convert.base64Decode( + 'CgZTdHJ1Y3QSOwoGZmllbGRzGAEgAygLMiMuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdC5GaWVsZH' + 'NFbnRyeVIGZmllbGRzGlEKC0ZpZWxkc0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EiwKBXZhbHVl' + 'GAIgASgLMhYuZ29vZ2xlLnByb3RvYnVmLlZhbHVlUgV2YWx1ZToCOAE='); + +@$core.Deprecated('Use valueDescriptor instead') +const Value$json = { + '1': 'Value', + '2': [ + {'1': 'null_value', '3': 1, '4': 1, '5': 14, '6': '.google.protobuf.NullValue', '9': 0, '10': 'nullValue'}, + {'1': 'number_value', '3': 2, '4': 1, '5': 1, '9': 0, '10': 'numberValue'}, + {'1': 'string_value', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'stringValue'}, + {'1': 'bool_value', '3': 4, '4': 1, '5': 8, '9': 0, '10': 'boolValue'}, + {'1': 'struct_value', '3': 5, '4': 1, '5': 11, '6': '.google.protobuf.Struct', '9': 0, '10': 'structValue'}, + {'1': 'list_value', '3': 6, '4': 1, '5': 11, '6': '.google.protobuf.ListValue', '9': 0, '10': 'listValue'}, + ], + '8': [ + {'1': 'kind'}, + ], +}; + +/// Descriptor for `Value`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List valueDescriptor = $convert.base64Decode( + 'CgVWYWx1ZRI7CgpudWxsX3ZhbHVlGAEgASgOMhouZ29vZ2xlLnByb3RvYnVmLk51bGxWYWx1ZU' + 'gAUgludWxsVmFsdWUSIwoMbnVtYmVyX3ZhbHVlGAIgASgBSABSC251bWJlclZhbHVlEiMKDHN0' + 'cmluZ192YWx1ZRgDIAEoCUgAUgtzdHJpbmdWYWx1ZRIfCgpib29sX3ZhbHVlGAQgASgISABSCW' + 'Jvb2xWYWx1ZRI8CgxzdHJ1Y3RfdmFsdWUYBSABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0' + 'SABSC3N0cnVjdFZhbHVlEjsKCmxpc3RfdmFsdWUYBiABKAsyGi5nb29nbGUucHJvdG9idWYuTG' + 'lzdFZhbHVlSABSCWxpc3RWYWx1ZUIGCgRraW5k'); + +@$core.Deprecated('Use listValueDescriptor instead') +const ListValue$json = { + '1': 'ListValue', + '2': [ + {'1': 'values', '3': 1, '4': 3, '5': 11, '6': '.google.protobuf.Value', '10': 'values'}, + ], +}; + +/// Descriptor for `ListValue`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List listValueDescriptor = $convert.base64Decode( + 'CglMaXN0VmFsdWUSLgoGdmFsdWVzGAEgAygLMhYuZ29vZ2xlLnByb3RvYnVmLlZhbHVlUgZ2YW' + 'x1ZXM='); + diff --git a/lib/pb/google/protobuf/struct.pbserver.dart b/lib/pb/google/protobuf/struct.pbserver.dart new file mode 100644 index 0000000..b92ae61 --- /dev/null +++ b/lib/pb/google/protobuf/struct.pbserver.dart @@ -0,0 +1,14 @@ +// +// Generated code. Do not modify. +// source: google/protobuf/struct.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +export 'struct.pb.dart'; + diff --git a/lib/pb/stillbox.pb.dart b/lib/pb/stillbox.pb.dart index ffea5ba..12bb708 100644 --- a/lib/pb/stillbox.pb.dart +++ b/lib/pb/stillbox.pb.dart @@ -14,6 +14,7 @@ import 'dart:core' as $core; import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:protobuf/protobuf.dart' as $pb; +import 'google/protobuf/struct.pb.dart' as $1; import 'google/protobuf/timestamp.pb.dart' as $0; import 'stillbox.pbenum.dart'; @@ -21,6 +22,7 @@ export 'stillbox.pbenum.dart'; enum Message_ToClientMessage { call, + tgInfo, notification, popup, error, @@ -30,6 +32,7 @@ enum Message_ToClientMessage { class Message extends $pb.GeneratedMessage { factory Message({ Call? call, + TalkgroupInfo? tgInfo, Notification? notification, UserPopup? popup, Error? error, @@ -38,6 +41,9 @@ class Message extends $pb.GeneratedMessage { if (call != null) { $result.call = call; } + if (tgInfo != null) { + $result.tgInfo = tgInfo; + } if (notification != null) { $result.notification = notification; } @@ -55,17 +61,19 @@ class Message extends $pb.GeneratedMessage { static const $core.Map<$core.int, Message_ToClientMessage> _Message_ToClientMessageByTag = { 1 : Message_ToClientMessage.call, - 2 : Message_ToClientMessage.notification, - 3 : Message_ToClientMessage.popup, - 4 : Message_ToClientMessage.error, + 2 : Message_ToClientMessage.tgInfo, + 3 : Message_ToClientMessage.notification, + 4 : Message_ToClientMessage.popup, + 5 : Message_ToClientMessage.error, 0 : Message_ToClientMessage.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Message', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create) - ..oo(0, [1, 2, 3, 4]) + ..oo(0, [1, 2, 3, 4, 5]) ..aOM(1, _omitFieldNames ? '' : 'call', subBuilder: Call.create) - ..aOM(2, _omitFieldNames ? '' : 'notification', subBuilder: Notification.create) - ..aOM(3, _omitFieldNames ? '' : 'popup', subBuilder: UserPopup.create) - ..aOM(4, _omitFieldNames ? '' : 'error', subBuilder: Error.create) + ..aOM(2, _omitFieldNames ? '' : 'tgInfo', protoName: 'tgInfo', subBuilder: TalkgroupInfo.create) + ..aOM(3, _omitFieldNames ? '' : 'notification', subBuilder: Notification.create) + ..aOM(4, _omitFieldNames ? '' : 'popup', subBuilder: UserPopup.create) + ..aOM(5, _omitFieldNames ? '' : 'error', subBuilder: Error.create) ..hasRequiredFields = false ; @@ -105,37 +113,48 @@ class Message extends $pb.GeneratedMessage { Call ensureCall() => $_ensure(0); @$pb.TagNumber(2) - Notification get notification => $_getN(1); + TalkgroupInfo get tgInfo => $_getN(1); @$pb.TagNumber(2) - set notification(Notification v) { setField(2, v); } + set tgInfo(TalkgroupInfo v) { setField(2, v); } @$pb.TagNumber(2) - $core.bool hasNotification() => $_has(1); + $core.bool hasTgInfo() => $_has(1); @$pb.TagNumber(2) - void clearNotification() => clearField(2); + void clearTgInfo() => clearField(2); @$pb.TagNumber(2) - Notification ensureNotification() => $_ensure(1); + TalkgroupInfo ensureTgInfo() => $_ensure(1); @$pb.TagNumber(3) - UserPopup get popup => $_getN(2); + Notification get notification => $_getN(2); @$pb.TagNumber(3) - set popup(UserPopup v) { setField(3, v); } + set notification(Notification v) { setField(3, v); } @$pb.TagNumber(3) - $core.bool hasPopup() => $_has(2); + $core.bool hasNotification() => $_has(2); @$pb.TagNumber(3) - void clearPopup() => clearField(3); + void clearNotification() => clearField(3); @$pb.TagNumber(3) - UserPopup ensurePopup() => $_ensure(2); + Notification ensureNotification() => $_ensure(2); @$pb.TagNumber(4) - Error get error => $_getN(3); + UserPopup get popup => $_getN(3); @$pb.TagNumber(4) - set error(Error v) { setField(4, v); } + set popup(UserPopup v) { setField(4, v); } @$pb.TagNumber(4) - $core.bool hasError() => $_has(3); + $core.bool hasPopup() => $_has(3); @$pb.TagNumber(4) - void clearError() => clearField(4); + void clearPopup() => clearField(4); @$pb.TagNumber(4) - Error ensureError() => $_ensure(3); + UserPopup ensurePopup() => $_ensure(3); + + @$pb.TagNumber(5) + Error get error => $_getN(4); + @$pb.TagNumber(5) + set error(Error v) { setField(5, v); } + @$pb.TagNumber(5) + $core.bool hasError() => $_has(4); + @$pb.TagNumber(5) + void clearError() => clearField(5); + @$pb.TagNumber(5) + Error ensureError() => $_ensure(4); } class Call extends $pb.GeneratedMessage { @@ -379,11 +398,15 @@ class UserPopup extends $pb.GeneratedMessage { class Error extends $pb.GeneratedMessage { factory Error({ $core.String? error, + Command? command, }) { final $result = create(); if (error != null) { $result.error = error; } + if (command != null) { + $result.command = command; + } return $result; } Error._() : super(); @@ -392,6 +415,7 @@ class Error extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Error', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'error') + ..aOM(2, _omitFieldNames ? '' : 'command', subBuilder: Command.create) ..hasRequiredFields = false ; @@ -424,6 +448,17 @@ class Error extends $pb.GeneratedMessage { $core.bool hasError() => $_has(0); @$pb.TagNumber(1) void clearError() => clearField(1); + + @$pb.TagNumber(2) + Command get command => $_getN(1); + @$pb.TagNumber(2) + set command(Command v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasCommand() => $_has(1); + @$pb.TagNumber(2) + void clearCommand() => clearField(2); + @$pb.TagNumber(2) + Command ensureCommand() => $_ensure(1); } class Notification extends $pb.GeneratedMessage { @@ -509,6 +544,7 @@ class Notification extends $pb.GeneratedMessage { enum Command_Command { liveCommand, searchCommand, + tgCommand, notSet } @@ -516,6 +552,7 @@ class Command extends $pb.GeneratedMessage { factory Command({ Live? liveCommand, Search? searchCommand, + Talkgroup? tgCommand, }) { final $result = create(); if (liveCommand != null) { @@ -524,6 +561,9 @@ class Command extends $pb.GeneratedMessage { if (searchCommand != null) { $result.searchCommand = searchCommand; } + if (tgCommand != null) { + $result.tgCommand = tgCommand; + } return $result; } Command._() : super(); @@ -533,12 +573,14 @@ class Command extends $pb.GeneratedMessage { static const $core.Map<$core.int, Command_Command> _Command_CommandByTag = { 1 : Command_Command.liveCommand, 2 : Command_Command.searchCommand, + 3 : Command_Command.tgCommand, 0 : Command_Command.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Command', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create) - ..oo(0, [1, 2]) + ..oo(0, [1, 2, 3]) ..aOM(1, _omitFieldNames ? '' : 'liveCommand', protoName: 'liveCommand', subBuilder: Live.create) ..aOM(2, _omitFieldNames ? '' : 'searchCommand', protoName: 'searchCommand', subBuilder: Search.create) + ..aOM(3, _omitFieldNames ? '' : 'tgCommand', protoName: 'tgCommand', subBuilder: Talkgroup.create) ..hasRequiredFields = false ; @@ -587,6 +629,149 @@ class Command extends $pb.GeneratedMessage { void clearSearchCommand() => clearField(2); @$pb.TagNumber(2) Search ensureSearchCommand() => $_ensure(1); + + @$pb.TagNumber(3) + Talkgroup get tgCommand => $_getN(2); + @$pb.TagNumber(3) + set tgCommand(Talkgroup v) { setField(3, v); } + @$pb.TagNumber(3) + $core.bool hasTgCommand() => $_has(2); + @$pb.TagNumber(3) + void clearTgCommand() => clearField(3); + @$pb.TagNumber(3) + Talkgroup ensureTgCommand() => $_ensure(2); +} + +class TalkgroupInfo extends $pb.GeneratedMessage { + factory TalkgroupInfo({ + Talkgroup? tg, + $core.String? name, + $core.String? group, + $core.int? frequency, + $core.Iterable<$core.String>? tags, + $1.Struct? metadata, + $core.bool? learned, + }) { + final $result = create(); + if (tg != null) { + $result.tg = tg; + } + if (name != null) { + $result.name = name; + } + if (group != null) { + $result.group = group; + } + if (frequency != null) { + $result.frequency = frequency; + } + if (tags != null) { + $result.tags.addAll(tags); + } + if (metadata != null) { + $result.metadata = metadata; + } + if (learned != null) { + $result.learned = learned; + } + return $result; + } + TalkgroupInfo._() : super(); + factory TalkgroupInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory TalkgroupInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'TalkgroupInfo', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'tg', subBuilder: Talkgroup.create) + ..aOS(2, _omitFieldNames ? '' : 'name') + ..aOS(3, _omitFieldNames ? '' : 'group') + ..a<$core.int>(4, _omitFieldNames ? '' : 'frequency', $pb.PbFieldType.O3) + ..pPS(5, _omitFieldNames ? '' : 'tags') + ..aOM<$1.Struct>(6, _omitFieldNames ? '' : 'metadata', subBuilder: $1.Struct.create) + ..aOB(7, _omitFieldNames ? '' : 'learned') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + TalkgroupInfo clone() => TalkgroupInfo()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + TalkgroupInfo copyWith(void Function(TalkgroupInfo) updates) => super.copyWith((message) => updates(message as TalkgroupInfo)) as TalkgroupInfo; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TalkgroupInfo create() => TalkgroupInfo._(); + TalkgroupInfo createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static TalkgroupInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static TalkgroupInfo? _defaultInstance; + + @$pb.TagNumber(1) + Talkgroup get tg => $_getN(0); + @$pb.TagNumber(1) + set tg(Talkgroup v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasTg() => $_has(0); + @$pb.TagNumber(1) + void clearTg() => clearField(1); + @$pb.TagNumber(1) + Talkgroup ensureTg() => $_ensure(0); + + @$pb.TagNumber(2) + $core.String get name => $_getSZ(1); + @$pb.TagNumber(2) + set name($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasName() => $_has(1); + @$pb.TagNumber(2) + void clearName() => clearField(2); + + @$pb.TagNumber(3) + $core.String get group => $_getSZ(2); + @$pb.TagNumber(3) + set group($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasGroup() => $_has(2); + @$pb.TagNumber(3) + void clearGroup() => clearField(3); + + @$pb.TagNumber(4) + $core.int get frequency => $_getIZ(3); + @$pb.TagNumber(4) + set frequency($core.int v) { $_setSignedInt32(3, v); } + @$pb.TagNumber(4) + $core.bool hasFrequency() => $_has(3); + @$pb.TagNumber(4) + void clearFrequency() => clearField(4); + + @$pb.TagNumber(5) + $core.List<$core.String> get tags => $_getList(4); + + @$pb.TagNumber(6) + $1.Struct get metadata => $_getN(5); + @$pb.TagNumber(6) + set metadata($1.Struct v) { setField(6, v); } + @$pb.TagNumber(6) + $core.bool hasMetadata() => $_has(5); + @$pb.TagNumber(6) + void clearMetadata() => clearField(6); + @$pb.TagNumber(6) + $1.Struct ensureMetadata() => $_ensure(5); + + @$pb.TagNumber(7) + $core.bool get learned => $_getBF(6); + @$pb.TagNumber(7) + set learned($core.bool v) { $_setBool(6, v); } + @$pb.TagNumber(7) + $core.bool hasLearned() => $_has(6); + @$pb.TagNumber(7) + void clearLearned() => clearField(7); } class Live extends $pb.GeneratedMessage { diff --git a/lib/pb/stillbox.pbjson.dart b/lib/pb/stillbox.pbjson.dart index adcd6bd..437c1aa 100644 --- a/lib/pb/stillbox.pbjson.dart +++ b/lib/pb/stillbox.pbjson.dart @@ -33,9 +33,10 @@ const Message$json = { '1': 'Message', '2': [ {'1': 'call', '3': 1, '4': 1, '5': 11, '6': '.stillbox.Call', '9': 0, '10': 'call'}, - {'1': 'notification', '3': 2, '4': 1, '5': 11, '6': '.stillbox.Notification', '9': 0, '10': 'notification'}, - {'1': 'popup', '3': 3, '4': 1, '5': 11, '6': '.stillbox.UserPopup', '9': 0, '10': 'popup'}, - {'1': 'error', '3': 4, '4': 1, '5': 11, '6': '.stillbox.Error', '9': 0, '10': 'error'}, + {'1': 'tgInfo', '3': 2, '4': 1, '5': 11, '6': '.stillbox.TalkgroupInfo', '9': 0, '10': 'tgInfo'}, + {'1': 'notification', '3': 3, '4': 1, '5': 11, '6': '.stillbox.Notification', '9': 0, '10': 'notification'}, + {'1': 'popup', '3': 4, '4': 1, '5': 11, '6': '.stillbox.UserPopup', '9': 0, '10': 'popup'}, + {'1': 'error', '3': 5, '4': 1, '5': 11, '6': '.stillbox.Error', '9': 0, '10': 'error'}, ], '8': [ {'1': 'toClient_message'}, @@ -44,10 +45,11 @@ const Message$json = { /// Descriptor for `Message`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List messageDescriptor = $convert.base64Decode( - 'CgdNZXNzYWdlEiQKBGNhbGwYASABKAsyDi5zdGlsbGJveC5DYWxsSABSBGNhbGwSPAoMbm90aW' - 'ZpY2F0aW9uGAIgASgLMhYuc3RpbGxib3guTm90aWZpY2F0aW9uSABSDG5vdGlmaWNhdGlvbhIr' - 'CgVwb3B1cBgDIAEoCzITLnN0aWxsYm94LlVzZXJQb3B1cEgAUgVwb3B1cBInCgVlcnJvchgEIA' - 'EoCzIPLnN0aWxsYm94LkVycm9ySABSBWVycm9yQhIKEHRvQ2xpZW50X21lc3NhZ2U='); + 'CgdNZXNzYWdlEiQKBGNhbGwYASABKAsyDi5zdGlsbGJveC5DYWxsSABSBGNhbGwSMQoGdGdJbm' + 'ZvGAIgASgLMhcuc3RpbGxib3guVGFsa2dyb3VwSW5mb0gAUgZ0Z0luZm8SPAoMbm90aWZpY2F0' + 'aW9uGAMgASgLMhYuc3RpbGxib3guTm90aWZpY2F0aW9uSABSDG5vdGlmaWNhdGlvbhIrCgVwb3' + 'B1cBgEIAEoCzITLnN0aWxsYm94LlVzZXJQb3B1cEgAUgVwb3B1cBInCgVlcnJvchgFIAEoCzIP' + 'LnN0aWxsYm94LkVycm9ySABSBWVycm9yQhIKEHRvQ2xpZW50X21lc3NhZ2U='); @$core.Deprecated('Use callDescriptor instead') const Call$json = { @@ -98,12 +100,14 @@ const Error$json = { '1': 'Error', '2': [ {'1': 'error', '3': 1, '4': 1, '5': 9, '10': 'error'}, + {'1': 'command', '3': 2, '4': 1, '5': 11, '6': '.stillbox.Command', '10': 'command'}, ], }; /// Descriptor for `Error`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List errorDescriptor = $convert.base64Decode( - 'CgVFcnJvchIUCgVlcnJvchgBIAEoCVIFZXJyb3I='); + 'CgVFcnJvchIUCgVlcnJvchgBIAEoCVIFZXJyb3ISKwoHY29tbWFuZBgCIAEoCzIRLnN0aWxsYm' + '94LkNvbW1hbmRSB2NvbW1hbmQ='); @$core.Deprecated('Use notificationDescriptor instead') const Notification$json = { @@ -127,6 +131,7 @@ const Command$json = { '2': [ {'1': 'liveCommand', '3': 1, '4': 1, '5': 11, '6': '.stillbox.Live', '9': 0, '10': 'liveCommand'}, {'1': 'searchCommand', '3': 2, '4': 1, '5': 11, '6': '.stillbox.Search', '9': 0, '10': 'searchCommand'}, + {'1': 'tgCommand', '3': 3, '4': 1, '5': 11, '6': '.stillbox.Talkgroup', '9': 0, '10': 'tgCommand'}, ], '8': [ {'1': 'command'}, @@ -137,7 +142,37 @@ const Command$json = { final $typed_data.Uint8List commandDescriptor = $convert.base64Decode( 'CgdDb21tYW5kEjIKC2xpdmVDb21tYW5kGAEgASgLMg4uc3RpbGxib3guTGl2ZUgAUgtsaXZlQ2' '9tbWFuZBI4Cg1zZWFyY2hDb21tYW5kGAIgASgLMhAuc3RpbGxib3guU2VhcmNoSABSDXNlYXJj' - 'aENvbW1hbmRCCQoHY29tbWFuZA=='); + 'aENvbW1hbmQSMwoJdGdDb21tYW5kGAMgASgLMhMuc3RpbGxib3guVGFsa2dyb3VwSABSCXRnQ2' + '9tbWFuZEIJCgdjb21tYW5k'); + +@$core.Deprecated('Use talkgroupInfoDescriptor instead') +const TalkgroupInfo$json = { + '1': 'TalkgroupInfo', + '2': [ + {'1': 'tg', '3': 1, '4': 1, '5': 11, '6': '.stillbox.Talkgroup', '10': 'tg'}, + {'1': 'name', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'name', '17': true}, + {'1': 'group', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'group', '17': true}, + {'1': 'frequency', '3': 4, '4': 1, '5': 5, '9': 2, '10': 'frequency', '17': true}, + {'1': 'tags', '3': 5, '4': 3, '5': 9, '10': 'tags'}, + {'1': 'metadata', '3': 6, '4': 1, '5': 11, '6': '.google.protobuf.Struct', '9': 3, '10': 'metadata', '17': true}, + {'1': 'learned', '3': 7, '4': 1, '5': 8, '10': 'learned'}, + ], + '8': [ + {'1': '_name'}, + {'1': '_group'}, + {'1': '_frequency'}, + {'1': '_metadata'}, + ], +}; + +/// Descriptor for `TalkgroupInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List talkgroupInfoDescriptor = $convert.base64Decode( + 'Cg1UYWxrZ3JvdXBJbmZvEiMKAnRnGAEgASgLMhMuc3RpbGxib3guVGFsa2dyb3VwUgJ0ZxIXCg' + 'RuYW1lGAIgASgJSABSBG5hbWWIAQESGQoFZ3JvdXAYAyABKAlIAVIFZ3JvdXCIAQESIQoJZnJl' + 'cXVlbmN5GAQgASgFSAJSCWZyZXF1ZW5jeYgBARISCgR0YWdzGAUgAygJUgR0YWdzEjgKCG1ldG' + 'FkYXRhGAYgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgDUghtZXRhZGF0YYgBARIYCgds' + 'ZWFybmVkGAcgASgIUgdsZWFybmVkQgcKBV9uYW1lQggKBl9ncm91cEIMCgpfZnJlcXVlbmN5Qg' + 'sKCV9tZXRhZGF0YQ=='); @$core.Deprecated('Use liveDescriptor instead') const Live$json = { diff --git a/lib/views/lcd.dart b/lib/views/lcd.dart index d749654..9d6ecdc 100644 --- a/lib/views/lcd.dart +++ b/lib/views/lcd.dart @@ -1,4 +1,7 @@ +import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; +import '../controller/stillbox.dart'; +import '../pb/stillbox.pb.dart'; class LCD extends StatefulWidget { const LCD({super.key}); @@ -8,16 +11,6 @@ class LCD extends StatefulWidget { } class _LCDState extends State { - String? _currentTG; - int? _currentTGID; - - void _setListening(String tg, int tgid) { - setState(() { - _currentTG = tg; - _currentTGID = tgid; - }); - } - @override Widget build(BuildContext context) { return Container( @@ -36,7 +29,27 @@ class _LCDState extends State { width: double.infinity, child: AspectRatio( aspectRatio: 16 / 9, - child: Container(), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: DefaultTextStyle( + style: const TextStyle( + color: Colors.black, + ), + child: Consumer(builder: (context, sb, child) { + if (sb.currentCall != null) { + return FutureBuilder( + future: sb.currentCall?.tg, + builder: (BuildContext context, + AsyncSnapshot tgi) { + return Text( + '${tgi.data?.name ?? sb.currentCall!.call.talkgroup}${tgi.data?.learned ?? false ? ' 📓' : ''}'); + }); + } + + return const Text(''); + }), + ), + ), )); } } diff --git a/protobuf/stillbox.proto b/protobuf/stillbox.proto index a3c3391..34de81f 100644 --- a/protobuf/stillbox.proto +++ b/protobuf/stillbox.proto @@ -3,13 +3,15 @@ package stillbox; option go_package = "./pb"; import "google/protobuf/timestamp.proto"; +import "google/protobuf/struct.proto"; message Message { oneof toClient_message { Call call = 1; - Notification notification = 2; - UserPopup popup = 3; - Error error = 4; + TalkgroupInfo tgInfo = 2; + Notification notification = 3; + UserPopup popup = 4; + Error error = 5; } } @@ -34,6 +36,7 @@ message UserPopup { message Error { string error = 1; + Command command = 2; } message Notification { @@ -46,9 +49,20 @@ message Command { oneof command { Live liveCommand = 1; Search searchCommand = 2; + Talkgroup tgCommand = 3; } } +message TalkgroupInfo { + Talkgroup tg = 1; + optional string name = 2; + optional string group = 3; + optional int32 frequency = 4; + repeated string tags = 5; + optional google.protobuf.Struct metadata = 6; + bool learned = 7; +} + enum LiveState { LS_STOPPED = 0; LS_LIVE = 1;