From 66e9a74f0e349f12137b8da38b13fd95bc060a49 Mon Sep 17 00:00:00 2001 From: Damien Albisson Date: Wed, 12 Jun 2024 16:35:29 +0200 Subject: [PATCH] add widget video and fix close connection broker properly --- lib/after_setup_pages/userspace_page.dart | 83 ++++----------- lib/userspace_widgets/ic_blc.dart | 23 +--- lib/userspace_widgets/ic_bpc.dart | 34 ++---- lib/userspace_widgets/ic_not_managed.dart | 2 +- lib/userspace_widgets/ic_powermeter.dart | 7 +- lib/userspace_widgets/ic_relay.dart | 24 +---- lib/userspace_widgets/ic_thermometer.dart | 13 +-- lib/userspace_widgets/ic_video.dart | 123 ++++++++++++++++++++++ lib/utils_widgets/appBar.dart | 25 +---- pubspec.yaml | 14 +-- 10 files changed, 181 insertions(+), 167 deletions(-) create mode 100644 lib/userspace_widgets/ic_video.dart diff --git a/lib/after_setup_pages/userspace_page.dart b/lib/after_setup_pages/userspace_page.dart index 9c2106f..91f57bf 100644 --- a/lib/after_setup_pages/userspace_page.dart +++ b/lib/after_setup_pages/userspace_page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'dart:convert'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; @@ -8,6 +10,7 @@ import 'package:panduza_sandbox_flutter/userspace_widgets/ic_bpc.dart'; import 'package:panduza_sandbox_flutter/userspace_widgets/ic_platform.dart'; import 'package:panduza_sandbox_flutter/userspace_widgets/ic_powermeter.dart'; import 'package:panduza_sandbox_flutter/userspace_widgets/ic_not_managed.dart'; +import 'package:panduza_sandbox_flutter/userspace_widgets/ic_video.dart'; // import '../widgets/interface_control/icw_bpc.dart'; @@ -23,13 +26,15 @@ class UserspacePage extends StatefulWidget { final BrokerConnectionInfo broker_connection_info; @override - _UserspacePageState createState() => _UserspacePageState(); + State createState() => _UserspacePageState(); } class _UserspacePageState extends State { List interfaces = []; Map channel = {}; + StreamSubscription>>? mqttSubscription; + bool interfaceAlreadyRegistered(InterfaceConnection ic) { for (var interface in interfaces) { @@ -37,7 +42,6 @@ class _UserspacePageState extends State { return true; } } - // print(ic.topic); return false; } @@ -52,6 +56,13 @@ class _UserspacePageState extends State { return null; } + @override + void dispose() { + mqttSubscription!.cancel(); + + super.dispose(); + } + @override void initState() { super.initState(); @@ -69,17 +80,9 @@ class _UserspacePageState extends State { // Run your async function here // await myAsyncFunction(); - widget.broker_connection_info.client.updates! + mqttSubscription = widget.broker_connection_info.client.updates! .listen((List> c) { - // final MqttMessage message = c[0].payload; - // print('Received from userspace from topic: ${c[0].topic}>'); - - // final string = binascii.b2a_base64(bytearray(data)).decode('utf-8'); - // print(message.toString()); - - // pza/*/atts/info - // print(c[0].topic); if (c![0].topic.startsWith("pza") & c![0].topic.endsWith("atts/info")) { final recMess = c![0].payload as MqttPublishMessage; @@ -92,8 +95,11 @@ class _UserspacePageState extends State { var jsonObject = json.decode(pt); + // print(jsonObject); + InterfaceConnection ic = InterfaceConnection( widget.broker_connection_info.client, topic, jsonObject["info"]); + if (!interfaceAlreadyRegistered(ic)) { if (ic.getType() != "device") { setState(() { @@ -106,36 +112,12 @@ class _UserspacePageState extends State { } return compareResult; }); - // interfaces = [...interfaces, ic]; }); } } } - // final payload = - // MqttPublishPayload.bytesToStringAsString(message.); - - // print('Received message:$payload from topic: ${c[0].topic}>'); - - // sort and put in my map - }); }); - - /* - Future.delayed(Duration(seconds: 2), () async { - List newInterfaces = []; - - newInterfaces.add(findPlatform() as InterfaceConnection); - - for (var interface in interfaces) { - if (interface.info["type"] != "platform") { - newInterfaces.add(interface); - } - } - - interfaces = newInterfaces; - }); - */ } // Build item of the grid that control the interfaces @@ -162,6 +144,8 @@ class _UserspacePageState extends State { return IcThermometer(ic); case "relay": return IcRelay(ic); + case "video": + return IcVideo(ic); default: print("!!!! $type"); return IcNotManaged(ic); @@ -179,35 +163,6 @@ class _UserspacePageState extends State { // appBar: getAppBar("UserSpace"), appBar: getAppBarUserSpace("Userspace", context), body: - /* - ListView.separated( - padding: const EdgeInsets.all(20), - itemCount: interfaces.length, - - itemBuilder: (BuildContext context, int index) { - return interfaceControlItemBuiler(context, index); - }, - separatorBuilder: (context, index) => const Divider(), - ) - */ - /* - Column( - // Start with the platform then display every device - children: [ - Center( - child: Container( - height: MediaQuery.sizeOf(context).height / 3, - width: MediaQuery.sizeOf(context).width / 3, - child: IcPlatform(findPlatform() as InterfaceConnection), - ) - ), - ] - ) - */ - - - - MasonryGridView.count( crossAxisCount: columns, mainAxisSpacing: 4, diff --git a/lib/userspace_widgets/ic_blc.dart b/lib/userspace_widgets/ic_blc.dart index e216d57..1476ee2 100644 --- a/lib/userspace_widgets/ic_blc.dart +++ b/lib/userspace_widgets/ic_blc.dart @@ -16,7 +16,8 @@ class IcBlc extends StatefulWidget { final InterfaceConnection _interfaceConnection; @override - _IcBlcState createState() => _IcBlcState(); + State createState() => _IcBlcState(); + } double value_to_double(dynamic value) { @@ -53,8 +54,6 @@ class _IcBlcState extends State { /// /// void onMqttMessage(List> c) { - // print("============"); - // print('Received ${c[0].topic} from ${widget._interfaceConnection.topic} '); // if (c[0].topic.startsWith(widget._interfaceConnection.topic)) { @@ -66,14 +65,9 @@ class _IcBlcState extends State { var jsonObject = json.decode(pt); - // print(jsonObject); - - // Map updateAtts = Map.from(_attsEffective); - setState(() { for (MapEntry atts in jsonObject.entries) { for (MapEntry field in atts.value.entries) { - // print('${atts.key} ${field.key} => ${field.value}'); switch (atts.key) { case "mode": @@ -186,14 +180,6 @@ class _IcBlcState extends State { } } }); - // print(updateAtts); - - // setState(() { - // _attsEffective = updateAtts; - // }); - // _attsEffective - - // print(jsonObject.runtimeType); } } else { // print('not good:'); @@ -210,11 +196,6 @@ class _IcBlcState extends State { Subscription? sub = widget._interfaceConnection.client .subscribe(attsTopic, MqttQos.atLeastOnce); - // if (sub != null) { - // print("coool !!"); - // } else { - // print("nullllll"); - // } } /// Perform MQTT Subscriptions at the start of the component diff --git a/lib/userspace_widgets/ic_bpc.dart b/lib/userspace_widgets/ic_bpc.dart index a32956e..b68f657 100644 --- a/lib/userspace_widgets/ic_bpc.dart +++ b/lib/userspace_widgets/ic_bpc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:panduza_sandbox_flutter/data/const.dart'; import 'templates.dart'; @@ -14,7 +16,8 @@ class IcBpc extends StatefulWidget { final InterfaceConnection _interfaceConnection; @override - _IcBpcState createState() => _IcBpcState(); + State createState() => _IcBpcState(); + } class _IcBpcState extends State { @@ -27,15 +30,14 @@ class _IcBpcState extends State { double? _currentValueReq; double? _currentValueEff; + StreamSubscription>>? mqttSubscription; + /// /// void onMqttMessage(List> c) { - // print("============"); - // print('Received ${c[0].topic} from ${widget._interfaceConnection.topic} '); // if (c[0].topic.startsWith(widget._interfaceConnection.topic)) { - // print(c[0].topic); if (!c[0].topic.endsWith('/info')) { final recMess = c![0].payload as MqttPublishMessage; @@ -44,14 +46,9 @@ class _IcBpcState extends State { var jsonObject = json.decode(pt); - // print(jsonObject); - - // Map updateAtts = Map.from(_attsEffective); - setState(() { for (MapEntry atts in jsonObject.entries) { for (MapEntry field in atts.value.entries) { - // print('${atts.key} ${field.key} => ${field.value}'); switch (atts.key) { case "enable": if (field.key == "value") { @@ -62,7 +59,6 @@ class _IcBpcState extends State { case "voltage": if (field.key == "value") { - // print("pokkk !! ${field.value.runtimeType}"); switch (field.value.runtimeType) { case int: _voltageValueEff = field.value.toDouble(); @@ -75,7 +71,6 @@ class _IcBpcState extends State { case "current": if (field.key == "value") { - // print("pokkk !! ${field.value.runtimeType}"); switch (field.value.runtimeType) { case int: _currentValueEff = field.value.toDouble(); @@ -89,14 +84,6 @@ class _IcBpcState extends State { } } }); - // print(updateAtts); - - // setState(() { - // _attsEffective = updateAtts; - // }); - // _attsEffective - - // print(jsonObject.runtimeType); } } else { // print('not good:'); @@ -106,18 +93,12 @@ class _IcBpcState extends State { /// Initialize MQTT Subscriptions /// void initializeMqttSubscription() async { - widget._interfaceConnection.client.updates!.listen(onMqttMessage); + mqttSubscription = widget._interfaceConnection.client.updates!.listen(onMqttMessage); String attsTopic = "${widget._interfaceConnection.topic}/atts/#"; // print(attsTopic); Subscription? sub = widget._interfaceConnection.client .subscribe(attsTopic, MqttQos.atLeastOnce); - - // if (sub != null) { - // print("coool !!"); - // } else { - // print("nullllll"); - // } } /// Perform MQTT Subscriptions at the start of the component @@ -132,6 +113,7 @@ class _IcBpcState extends State { @override void dispose() { + mqttSubscription!.cancel(); super.dispose(); } diff --git a/lib/userspace_widgets/ic_not_managed.dart b/lib/userspace_widgets/ic_not_managed.dart index d26477f..214cb8e 100644 --- a/lib/userspace_widgets/ic_not_managed.dart +++ b/lib/userspace_widgets/ic_not_managed.dart @@ -8,7 +8,7 @@ class IcNotManaged extends StatefulWidget { InterfaceConnection _interfaceConnection; @override - _IcNotManagedState createState() => _IcNotManagedState(); + State createState() => _IcNotManagedState(); } class _IcNotManagedState extends State { diff --git a/lib/userspace_widgets/ic_powermeter.dart b/lib/userspace_widgets/ic_powermeter.dart index c4cd77e..d4c6e5f 100644 --- a/lib/userspace_widgets/ic_powermeter.dart +++ b/lib/userspace_widgets/ic_powermeter.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:panduza_sandbox_flutter/data/const.dart'; @@ -22,6 +24,8 @@ class _IcPowermeterState extends State { double _value = 0; + StreamSubscription>>? mqttSubscription; + /// Init each value of the powermeter, here just the measure /// powermeter /// @@ -74,7 +78,7 @@ class _IcPowermeterState extends State { /// Initialize MQTT Subscriptions /// void initializeMqttSubscription() async { - widget._interfaceConnection.client.updates!.listen(onMqttMessage); + mqttSubscription = widget._interfaceConnection.client.updates!.listen(onMqttMessage); String attsTopic = "${widget._interfaceConnection.topic}/atts/#"; // print(attsTopic); @@ -100,6 +104,7 @@ class _IcPowermeterState extends State { @override void dispose() { + mqttSubscription!.cancel(); super.dispose(); } diff --git a/lib/userspace_widgets/ic_relay.dart b/lib/userspace_widgets/ic_relay.dart index 75ca5a3..3a0f825 100644 --- a/lib/userspace_widgets/ic_relay.dart +++ b/lib/userspace_widgets/ic_relay.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -23,18 +24,14 @@ class _IcRelayState extends State { bool? _enableValueReq = false; bool? _enableValueEff = false; + StreamSubscription>>? mqttSubscription; + /// /// void onMqttMessage(List> c) { - // print("============"); - // print('Received ${c[0].topic} from ${widget._interfaceConnection.topic} '); - - // print(widget._interfaceConnection.topic); if (c[0].topic.startsWith(widget._interfaceConnection.topic)) { - // print("test = ${c[0].topic}"); if (!c[0].topic.endsWith('/info')) { - // print("success = ${c[0].topic}"); final recMess = c![0].payload as MqttPublishMessage; final pt = @@ -42,10 +39,6 @@ class _IcRelayState extends State { var jsonObject = json.decode(pt); - // print(jsonObject); - - // Map updateAtts = Map.from(_attsEffective); - setState(() { for (MapEntry atts in jsonObject.entries) { for (MapEntry field in atts.value.entries) { @@ -75,18 +68,18 @@ class _IcRelayState extends State { // because first state is not detected (and the application see it like // interface is not connected) - widget._interfaceConnection.client.updates!.listen(onMqttMessage); + mqttSubscription = widget._interfaceConnection.client.updates!.listen(onMqttMessage); await Future.delayed(const Duration(milliseconds: 400)); String attsTopic = "${widget._interfaceConnection.topic}/atts/#"; - // print(attsTopic); Subscription? sub = widget._interfaceConnection.client .subscribe(attsTopic, MqttQos.atLeastOnce); } @override void dispose() { + mqttSubscription!.cancel(); super.dispose(); } @@ -159,13 +152,6 @@ class _IcRelayState extends State { onChanged: enableValueSwitchOnChanged(), activeColor: blue, ), - /* - Switch( - value: _enableValueEff, - onChanged: (value) { - }, - ) - */ ], ) ); diff --git a/lib/userspace_widgets/ic_thermometer.dart b/lib/userspace_widgets/ic_thermometer.dart index e8cae01..71717a0 100644 --- a/lib/userspace_widgets/ic_thermometer.dart +++ b/lib/userspace_widgets/ic_thermometer.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:panduza_sandbox_flutter/data/const.dart'; @@ -21,13 +23,13 @@ class IcThermometer extends StatefulWidget { class _IcThermometerState extends State { double _value = 0; + + StreamSubscription>>? mqttSubscription; /// Init each value of the thermometer, here just the measure /// temperature /// void onMqttMessage(List> c) { - print("============"); - print('Received ${c[0].topic} from ${widget._interfaceConnection.topic} '); // if (c[0].topic.startsWith(widget._interfaceConnection.topic)) { @@ -38,12 +40,9 @@ class _IcThermometerState extends State { MqttPublishPayload.bytesToStringAsString(recMess.payload.message); var jsonObject = json.decode(pt); - - print(jsonObject); for (MapEntry atts in jsonObject.entries) { for (MapEntry field in atts.value.entries) { - print('${atts.key} ${field.key} => ${field.value}'); switch (atts.key) { case "measure": @@ -77,7 +76,8 @@ class _IcThermometerState extends State { /// Initialize MQTT Subscriptions /// void initializeMqttSubscription() async { - widget._interfaceConnection.client.updates!.listen(onMqttMessage); + + mqttSubscription = widget._interfaceConnection.client.updates!.listen(onMqttMessage); String attsTopic = "${widget._interfaceConnection.topic}/atts/#"; // print(attsTopic); @@ -103,6 +103,7 @@ class _IcThermometerState extends State { @override void dispose() { + mqttSubscription!.cancel(); super.dispose(); } diff --git a/lib/userspace_widgets/ic_video.dart b/lib/userspace_widgets/ic_video.dart new file mode 100644 index 0000000..976a457 --- /dev/null +++ b/lib/userspace_widgets/ic_video.dart @@ -0,0 +1,123 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:mqtt_client/mqtt_client.dart'; +import 'package:panduza_sandbox_flutter/data/interface_connection.dart'; + +import 'package:panduza_sandbox_flutter/userspace_widgets/templates.dart'; + +class IcVideo extends StatefulWidget { + const IcVideo(this._interfaceConnection, + {super.key} + ); + + final InterfaceConnection _interfaceConnection; + + @override + State createState() => _IcVideoState(); +} + +class _IcVideoState extends State { + + int imageCount = 0; + + // Video Stream + StreamController streamVideoController = StreamController(); + + // Subcription to broker message + StreamSubscription>>? mqttSubscription; + + // init the receving of frame on some topic (maybe add some topic) + + void initFrameRecevingMqtt() { + Future.delayed(Duration(milliseconds: 1), () { + + // Subscribe to receive message link to video + String attsTopic = "${widget._interfaceConnection.topic}/atts/#"; + widget._interfaceConnection.client + .subscribe(attsTopic, MqttQos.atLeastOnce); + + MqttClientPayloadBuilder builder = MqttClientPayloadBuilder(); + builder.addString('*'); + final payload = builder.payload; + widget._interfaceConnection.client + .publishMessage('pza', MqttQos.atLeastOnce, payload!); + + print(widget._interfaceConnection.topic); + + if (streamVideoController.isClosed) { + streamVideoController = StreamController(); + print("reopen stream video"); + } + + // Listen every frame on the broker + mqttSubscription = widget._interfaceConnection.client.updates! + .listen((List> c) { + + if (c![0].topic == "${widget._interfaceConnection.topic}/atts/frame") { + + final recMess = c![0].payload as MqttPublishMessage; + + // Reading frame and putting it on the video stream + + // From mjpeg + + var frameMjpeg = recMess.payload.message; + var imageBytes = Uint8List.view(frameMjpeg.buffer, 0, frameMjpeg.length); + streamVideoController.add(imageBytes); + + // From h264 + } + }); + }); + } + + // Start to receive video at init + @override + void initState() { + super.initState(); + initFrameRecevingMqtt(); + } + + // Stop listening broker message and close video stream + @override + void dispose() { + mqttSubscription!.cancel(); + streamVideoController.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: streamVideoController.stream, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + var imageData = Uint8List.fromList(snapshot.data); + return Card( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + cardHeadLine(widget._interfaceConnection), + SizedBox( + height: 200, + width: 300, + child: FittedBox( + fit: BoxFit.fill, + child: Image.memory( + imageData, + gaplessPlayback: true, + ), + ), + ) + ], + ) + ); + } else { + return const Card(); + } + }, + ); + } +} \ No newline at end of file diff --git a/lib/utils_widgets/appBar.dart b/lib/utils_widgets/appBar.dart index 66e7832..5c49b1a 100644 --- a/lib/utils_widgets/appBar.dart +++ b/lib/utils_widgets/appBar.dart @@ -3,7 +3,6 @@ import 'package:panduza_sandbox_flutter/after_setup_pages/connections_page.dart' import 'package:panduza_sandbox_flutter/data/const.dart'; import 'package:flutter/material.dart'; -import 'package:panduza_sandbox_flutter/data/const.dart'; import 'package:panduza_sandbox_flutter/setup_pages/cloud_config_auth_page.dart'; // bar at the top of the application on nearly every page @@ -19,16 +18,9 @@ PreferredSizeWidget? getAppBar(String title) { color: blue, ), ), - // Panduza logo - // TO DO : Change to logo2 actions: [ IconButton( icon: Image.asset('assets/logo_circle_black_blue_1024.png'), - /* - icon: SvgPicture.asset( - '../../assets/icons/logo2.svg' - ), - */ iconSize: 50, onPressed: () { return; @@ -50,16 +42,9 @@ PreferredSizeWidget? getAppBarUserSpace(String title, BuildContext context) { color: blue, ), ), - // Panduza logo - // TO DO : Change to logo2 actions: [ IconButton( icon: Image.asset('assets/logo_circle_black_blue_1024.png'), - /* - icon: SvgPicture.asset( - '../../assets/icons/logo2.svg' - ), - */ iconSize: 50, onPressed: () { return; @@ -69,7 +54,8 @@ PreferredSizeWidget? getAppBarUserSpace(String title, BuildContext context) { leading: BackButton( color: white, onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const ConnectionsPage())); + // Navigator.push(context, MaterialPageRoute(builder: (context) => const ConnectionsPage())); + Navigator.pop(context); }, ), ); @@ -87,8 +73,6 @@ PreferredSizeWidget? getConnectionsAppBar(String title, BuildContext context) { color: blue, ), ), - // Panduza logo - // TO DO : Change to logo2 actions: [ IconButton( onPressed: () { @@ -104,11 +88,6 @@ PreferredSizeWidget? getConnectionsAppBar(String title, BuildContext context) { ), IconButton( icon: Image.asset('assets/logo_circle_black_blue_1024.png'), - /* - icon: SvgPicture.asset( - '../../assets/icons/logo2.svg' - ), - */ iconSize: 50, onPressed: () { return; diff --git a/pubspec.yaml b/pubspec.yaml index 0b566e5..86b96a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,17 +34,19 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - mqtt_client: ^10.0.0 + cupertino_icons: ^1.0.8 + mqtt_client: ^10.2.1 flutter_launcher_icons: ^0.13.1 - multi_split_view: ^2.4.0 + multi_split_view: ^3.1.0 flutter_staggered_grid_view: ^0.7.0 udp: ^5.0.3 flutter_svg: ^2.0.10+1 - shared_preferences: ^2.2.2 + shared_preferences: ^2.2.3 auto_size_text: ^3.0.0 - fluttertoast: ^8.2.4 + fluttertoast: ^8.2.6 http: ^1.2.1 + yuv_to_png: ^0.0.2 + yuv_converter: ^0.0.2 dev_dependencies: flutter_test: @@ -55,7 +57,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec