diff --git a/lib/Helpers/MQTTHelper.dart b/lib/Helpers/MQTTHelper.dart index 29a3424..e9cdd24 100644 --- a/lib/Helpers/MQTTHelper.dart +++ b/lib/Helpers/MQTTHelper.dart @@ -1,18 +1,67 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:managerapi/api.dart'; import 'package:mqtt_client/mqtt_client.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; +import 'package:tablet_app/Models/tabletContext.dart'; import 'package:unique_identifier/unique_identifier.dart'; +import 'DatabaseHelper.dart'; + class MQTTHelper { MQTTHelper._privateConstructor(); + bool isInstantiated = false; static final MQTTHelper instance = MQTTHelper._privateConstructor(); - void onConnected(dynamic tabletAppContext) { + void onConnected(dynamic appContext) { print('Connected !!!!!!!!!!!! ----------------------------------'); - + TabletAppContext tabletAppContext = appContext.getContext(); tabletAppContext.clientMQTT.updates.listen((List> c) { final MqttPublishMessage message = c[0].payload; final payload = MqttPublishPayload.bytesToStringAsString(message.payload.message); print('Received message:$payload from topic: ${c[0].topic}'); + + var topic = c[0].topic.split('/')[0]; + + switch(topic) { + case "config": + print('Get message in topic config = $payload'); + var configId = c[0].topic.split('/')[1]; + if (configId != null) { + // Check if tablet config + if (tabletAppContext.configuration.id == configId) { + // refresh config + try { + PlayerMessageDTO playerMessage = PlayerMessageDTO.fromJson(jsonDecode(payload)); + var isConfigChanged = playerMessage.configChanged != null ? playerMessage.configChanged : false; + if (isConfigChanged) { + updateConfig(appContext); + } + } + catch(e) { + print('Error = $e'); + } + } + } + break; + case "player": + print('Get message in topic player = $payload'); + + // refresh device info + try { + PlayerMessageDTO playerMessage = PlayerMessageDTO.fromJson(jsonDecode(payload)); + var isConfigChanged = playerMessage.configChanged != null ? playerMessage.configChanged : false; + if (isConfigChanged) { + updateDevice(appContext); + } + } + catch(e) { + print('Error = $e'); + } + break; + } }); } @@ -36,20 +85,28 @@ class MQTTHelper { print('Unsubscribed topic: $topic'); } - Future connect(dynamic tabletAppContext) async { + Future connect(dynamic appContext) async { + TabletAppContext tabletAppContext = appContext.getContext(); + var identifier = await UniqueIdentifier.serial; tabletAppContext.clientMQTT = MqttServerClient.withPort(tabletAppContext.host.replaceAll('http://', ''), 'tablet_app_'+identifier, 1883); + isInstantiated = true; tabletAppContext.clientMQTT.logging(on: false); tabletAppContext.clientMQTT.keepAlivePeriod = 20; tabletAppContext.clientMQTT.onDisconnected = onDisconnected; - tabletAppContext.clientMQTT.onConnected = () => onConnected(tabletAppContext); + tabletAppContext.clientMQTT.onConnected = () => onConnected(appContext); tabletAppContext.clientMQTT.onSubscribed = onSubscribed; + var message = { + "deviceId": tabletAppContext.deviceId != null ? tabletAppContext.deviceId : identifier, + "connected": false + }; + final connMessage = MqttConnectMessage().authenticateAs('admin', 'mdlf2021!') // TODO ONLINE .keepAliveFor(60) - .withWillTopic('willtopic') - .withWillMessage('Will message') + .withWillTopic('player/status') + .withWillMessage(jsonEncode(message)) .withClientIdentifier('tablet_app_'+identifier) .startClean() .withWillQos(MqttQos.atLeastOnce); @@ -58,12 +115,27 @@ class MQTTHelper { tabletAppContext.clientMQTT.autoReconnect = true; try { await tabletAppContext.clientMQTT.connect(); - tabletAppContext.clientMQTT.subscribe("#", MqttQos.atLeastOnce); - const pubTopic = 'player/status'; - final builder = MqttClientPayloadBuilder(); - builder.addString('tablet_app_'+identifier); - tabletAppContext.clientMQTT.publishMessage(pubTopic, MqttQos.atLeastOnce, builder.payload); + // For get config changed request + if(tabletAppContext.configuration != null) { + tabletAppContext.clientMQTT.subscribe('config/#', MqttQos.atLeastOnce); + } + + // For get device assignation config request + if(tabletAppContext.deviceId != null) { + tabletAppContext.clientMQTT.subscribe('player/${tabletAppContext.deviceId}', MqttQos.atLeastOnce); + + var message = { + "deviceId": tabletAppContext.deviceId, + "connected": true + }; + + const pubTopic = 'player/status'; + final builder = MqttClientPayloadBuilder(); + + builder.addString(jsonEncode(message)); + tabletAppContext.clientMQTT.publishMessage(pubTopic, MqttQos.atLeastOnce, builder.payload); + } } catch (e) { print('Exception: $e'); tabletAppContext.clientMQTT.disconnect(); @@ -71,4 +143,87 @@ class MQTTHelper { return tabletAppContext.clientMQTT; } + + Future updateDevice(dynamic appContext) async { + print("updateDevice"); + + TabletAppContext tabletAppContext = appContext.getContext(); + DeviceDetailDTO device = await tabletAppContext.clientAPI.deviceApi.deviceGetDetail(tabletAppContext.deviceId); + + print(device); + + if (device != null) { + // STORE IT LOCALLY !! + TabletAppContext tabletAppContext = appContext.getContext(); + tabletAppContext.deviceId = device.id; + tabletAppContext.configuration.id = device.configurationId; + + appContext.setContext(tabletAppContext); + + // STORE IT LOCALLY (SQLite) + await DatabaseHelper.instance.update(tabletAppContext); + + Fluttertoast.showToast( + msg: "La tablette a bien été mise à jour", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 1, + backgroundColor: Colors.lightGreen, + textColor: Colors.white, + fontSize: 16.0 + ); + } else { + Fluttertoast.showToast( + msg: "Une erreur est survenue lors de la mise à jour de la tablette", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 1, + backgroundColor: Colors.deepOrangeAccent, + textColor: Colors.white, + fontSize: 16.0 + ); + } + } + + void updateConfig(appContext) async { + print("update config"); + + TabletAppContext tabletAppContext = appContext.getContext(); + ConfigurationDTO configuration = await tabletAppContext.clientAPI.configurationApi.configurationGetDetail(tabletAppContext.configuration.id); + + if (configuration != null) { + // STORE IT LOCALLY !! + TabletAppContext tabletAppContext = appContext.getContext(); + tabletAppContext.configuration = configuration; + + if(!configuration.languages.contains(tabletAppContext.language)) { + tabletAppContext.language = configuration.languages[0]; + } + + appContext.setContext(tabletAppContext); + + // STORE IT LOCALLY (SQLite) + await DatabaseHelper.instance.update(tabletAppContext); + + Fluttertoast.showToast( + msg: "La configuration a bien été mise à jour", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 1, + backgroundColor: Colors.lightGreen, + textColor: Colors.white, + fontSize: 16.0 + ); + } else { + Fluttertoast.showToast( + msg: "Une erreur est survenue lors de la mise à jour de la configuration", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 1, + backgroundColor: Colors.deepOrangeAccent, + textColor: Colors.white, + fontSize: 16.0 + ); + } + } } \ No newline at end of file diff --git a/lib/Screens/Configuration/config_view.dart b/lib/Screens/Configuration/config_view.dart index 5eaf974..75cae77 100644 --- a/lib/Screens/Configuration/config_view.dart +++ b/lib/Screens/Configuration/config_view.dart @@ -9,7 +9,6 @@ import 'package:tablet_app/Components/Buttons/rounded_button.dart'; import 'package:tablet_app/Components/loading.dart'; import 'package:tablet_app/Components/rounded_input_field.dart'; import 'package:tablet_app/Helpers/DatabaseHelper.dart'; -import 'package:tablet_app/Helpers/MQTTHelper.dart'; import 'package:tablet_app/Models/tabletContext.dart'; import 'package:tablet_app/Screens/MainView/dropDown_configuration.dart'; import 'package:tablet_app/Screens/MainView/main_view.dart'; @@ -156,7 +155,8 @@ class _ConfigViewWidget extends State { TabletAppContext tabletAppContext = new TabletAppContext(); tabletAppContext.host = url; tabletAppContext.clientAPI = client; - tabletAppContext.clientMQTT = await MQTTHelper.instance.connect(tabletAppContext); + var identifier = await UniqueIdentifier.serial; + tabletAppContext.clientMQTT = new MqttServerClient(url.replaceAll('http://', ''),'tablet_app_'+identifier); setState(() { appContext.setContext(tabletAppContext); configOk = true; diff --git a/lib/Screens/MainView/main_view.dart b/lib/Screens/MainView/main_view.dart index 2e73a19..3078598 100644 --- a/lib/Screens/MainView/main_view.dart +++ b/lib/Screens/MainView/main_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:managerapi/api.dart'; import 'package:provider/provider.dart'; import 'package:tablet_app/Components/loading.dart'; +import 'package:tablet_app/Helpers/MQTTHelper.dart'; import 'package:tablet_app/Screens/Map/map_context.dart'; import 'package:tablet_app/Screens/Map/map_view.dart'; import 'package:tablet_app/Models/map-marker.dart'; @@ -38,6 +39,9 @@ class _MainViewWidget extends State { SystemChrome.setEnabledSystemUIOverlays([]); Size size = MediaQuery.of(context).size; + if (!MQTTHelper.instance.isInstantiated) + MQTTHelper.instance.connect(appContext); + if(sectionSelected != null) { var elementToShow; switch (sectionSelected.type) { diff --git a/lib/main.dart b/lib/main.dart index 431b687..a26bb80 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,7 +29,6 @@ void main() async { // Get config from manager DeviceDetailDTO device = await localContext.clientAPI.deviceApi.deviceGetDetail(localContext.deviceId); localContext.configuration.id = device.configurationId; - } else { print("NO LOCAL DB !"); } @@ -63,8 +62,6 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { - if (widget.tabletAppContext != null) - MQTTHelper.instance.connect(widget.tabletAppContext); return ChangeNotifierProvider( create: (_) => AppContext(widget.tabletAppContext), child: MaterialApp( diff --git a/manager_api/.openapi-generator/FILES b/manager_api/.openapi-generator/FILES index 5271cf2..5767340 100644 --- a/manager_api/.openapi-generator/FILES +++ b/manager_api/.openapi-generator/FILES @@ -15,6 +15,7 @@ doc/LoginDTO.md doc/MapDTO.md doc/MapTypeApp.md doc/MenuDTO.md +doc/PlayerMessageDTO.md doc/ResourceApi.md doc/ResourceDTO.md doc/ResourceDetailDTO.md @@ -57,6 +58,7 @@ lib/model/login_dto.dart lib/model/map_dto.dart lib/model/map_type_app.dart lib/model/menu_dto.dart +lib/model/player_message_dto.dart lib/model/resource_detail_dto.dart lib/model/resource_dto.dart lib/model/resource_type.dart @@ -70,4 +72,3 @@ lib/model/user_detail_dto.dart lib/model/video_dto.dart lib/model/web_dto.dart pubspec.yaml -test/map_type_app_test.dart diff --git a/manager_api/README.md b/manager_api/README.md index 61935fe..269c300 100644 --- a/manager_api/README.md +++ b/manager_api/README.md @@ -96,6 +96,7 @@ Class | Method | HTTP request | Description *SectionApi* | [**sectionGetSliderDTO**](doc\/SectionApi.md#sectiongetsliderdto) | **GET** /api/Section/SliderDTO | *SectionApi* | [**sectionGetVideoDTO**](doc\/SectionApi.md#sectiongetvideodto) | **GET** /api/Section/VideoDTO | *SectionApi* | [**sectionGetWebDTO**](doc\/SectionApi.md#sectiongetwebdto) | **GET** /api/Section/WebDTO | +*SectionApi* | [**sectionPlayerMessageDTO**](doc\/SectionApi.md#sectionplayermessagedto) | **GET** /api/Section/PlayerMessageDTO | *SectionApi* | [**sectionUpdate**](doc\/SectionApi.md#sectionupdate) | **PUT** /api/Section | *SectionApi* | [**sectionUpdateOrder**](doc\/SectionApi.md#sectionupdateorder) | **PUT** /api/Section/order | *UserApi* | [**userCreateUser**](doc\/UserApi.md#usercreateuser) | **POST** /api/User | @@ -118,6 +119,7 @@ Class | Method | HTTP request | Description - [MapDTO](doc\/MapDTO.md) - [MapTypeApp](doc\/MapTypeApp.md) - [MenuDTO](doc\/MenuDTO.md) + - [PlayerMessageDTO](doc\/PlayerMessageDTO.md) - [ResourceDTO](doc\/ResourceDTO.md) - [ResourceDetailDTO](doc\/ResourceDetailDTO.md) - [ResourceType](doc\/ResourceType.md) diff --git a/manager_api/doc/PlayerMessageDTO.md b/manager_api/doc/PlayerMessageDTO.md new file mode 100644 index 0000000..0f07218 --- /dev/null +++ b/manager_api/doc/PlayerMessageDTO.md @@ -0,0 +1,16 @@ +# managerapi.model.PlayerMessageDTO + +## Load the model package +```dart +import 'package:managerapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**configChanged** | **bool** | | [optional] +**isDeleted** | **bool** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/manager_api/doc/SectionApi.md b/manager_api/doc/SectionApi.md index f65ea2e..aecedef 100644 --- a/manager_api/doc/SectionApi.md +++ b/manager_api/doc/SectionApi.md @@ -21,6 +21,7 @@ Method | HTTP request | Description [**sectionGetSliderDTO**](SectionApi.md#sectiongetsliderdto) | **GET** /api/Section/SliderDTO | [**sectionGetVideoDTO**](SectionApi.md#sectiongetvideodto) | **GET** /api/Section/VideoDTO | [**sectionGetWebDTO**](SectionApi.md#sectiongetwebdto) | **GET** /api/Section/WebDTO | +[**sectionPlayerMessageDTO**](SectionApi.md#sectionplayermessagedto) | **GET** /api/Section/PlayerMessageDTO | [**sectionUpdate**](SectionApi.md#sectionupdate) | **PUT** /api/Section | [**sectionUpdateOrder**](SectionApi.md#sectionupdateorder) | **PUT** /api/Section/order | @@ -517,6 +518,45 @@ This endpoint does not need any parameter. [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **sectionPlayerMessageDTO** +> PlayerMessageDTO sectionPlayerMessageDTO() + + + +### Example +```dart +import 'package:managerapi/api.dart'; +// TODO Configure OAuth2 access token for authorization: bearer +//defaultApiClient.getAuthentication('bearer').accessToken = 'YOUR_ACCESS_TOKEN'; + +final api_instance = SectionApi(); + +try { + final result = api_instance.sectionPlayerMessageDTO(); + print(result); +} catch (e) { + print('Exception when calling SectionApi->sectionPlayerMessageDTO: $e\n'); +} +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**PlayerMessageDTO**](PlayerMessageDTO.md) + +### Authorization + +[bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **sectionUpdate** > SectionDTO sectionUpdate(sectionDTO) diff --git a/manager_api/lib/api.dart b/manager_api/lib/api.dart index ae74ca1..ad57cca 100644 --- a/manager_api/lib/api.dart +++ b/manager_api/lib/api.dart @@ -45,6 +45,7 @@ part 'model/login_dto.dart'; part 'model/map_dto.dart'; part 'model/map_type_app.dart'; part 'model/menu_dto.dart'; +part 'model/player_message_dto.dart'; part 'model/resource_dto.dart'; part 'model/resource_detail_dto.dart'; part 'model/resource_type.dart'; diff --git a/manager_api/lib/api/section_api.dart b/manager_api/lib/api/section_api.dart index ace0a5f..caace06 100644 --- a/manager_api/lib/api/section_api.dart +++ b/manager_api/lib/api/section_api.dart @@ -716,6 +716,58 @@ class SectionApi { return Future.value(null); } + /// Performs an HTTP 'GET /api/Section/PlayerMessageDTO' operation and returns the [Response]. + Future sectionPlayerMessageDTOWithHttpInfo() async { + final path = r'/api/Section/PlayerMessageDTO'; + + Object postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + final contentTypes = []; + final nullableContentType = contentTypes.isNotEmpty ? contentTypes[0] : null; + final authNames = ['bearer']; + + if ( + nullableContentType != null && + nullableContentType.toLowerCase().startsWith('multipart/form-data') + ) { + bool hasFields = false; + final mp = MultipartRequest(null, null); + if (hasFields) { + postBody = mp; + } + } else { + } + + return await apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + nullableContentType, + authNames, + ); + } + + Future sectionPlayerMessageDTO() async { + final response = await sectionPlayerMessageDTOWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body != null && response.statusCode != HttpStatus.noContent) { + return apiClient.deserialize(_decodeBodyBytes(response), 'PlayerMessageDTO') as PlayerMessageDTO; + } + return Future.value(null); + } + /// Performs an HTTP 'PUT /api/Section' operation and returns the [Response]. /// Parameters: /// diff --git a/manager_api/lib/api_client.dart b/manager_api/lib/api_client.dart index b2e9dc9..4fe0d20 100644 --- a/manager_api/lib/api_client.dart +++ b/manager_api/lib/api_client.dart @@ -179,6 +179,8 @@ class ApiClient { case 'MenuDTO': return MenuDTO.fromJson(value); + case 'PlayerMessageDTO': + return PlayerMessageDTO.fromJson(value); case 'ResourceDTO': return ResourceDTO.fromJson(value); case 'ResourceDetailDTO': diff --git a/manager_api/lib/model/player_message_dto.dart b/manager_api/lib/model/player_message_dto.dart new file mode 100644 index 0000000..0341a99 --- /dev/null +++ b/manager_api/lib/model/player_message_dto.dart @@ -0,0 +1,80 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.0 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PlayerMessageDTO { + /// Returns a new [PlayerMessageDTO] instance. + PlayerMessageDTO({ + this.configChanged, + this.isDeleted, + }); + + bool configChanged; + + bool isDeleted; + + @override + bool operator ==(Object other) => identical(this, other) || other is PlayerMessageDTO && + other.configChanged == configChanged && + other.isDeleted == isDeleted; + + @override + int get hashCode => + (configChanged == null ? 0 : configChanged.hashCode) + + (isDeleted == null ? 0 : isDeleted.hashCode); + + @override + String toString() => 'PlayerMessageDTO[configChanged=$configChanged, isDeleted=$isDeleted]'; + + Map toJson() { + final json = {}; + if (configChanged != null) { + json[r'configChanged'] = configChanged; + } + if (isDeleted != null) { + json[r'isDeleted'] = isDeleted; + } + return json; + } + + /// Returns a new [PlayerMessageDTO] instance and imports its values from + /// [json] if it's non-null, null if [json] is null. + static PlayerMessageDTO fromJson(Map json) => json == null + ? null + : PlayerMessageDTO( + configChanged: json[r'configChanged'], + isDeleted: json[r'isDeleted'], + ); + + static List listFromJson(List json, {bool emptyIsNull, bool growable,}) => + json == null || json.isEmpty + ? true == emptyIsNull ? null : [] + : json.map((v) => PlayerMessageDTO.fromJson(v)).toList(growable: true == growable); + + static Map mapFromJson(Map json) { + final map = {}; + if (json != null && json.isNotEmpty) { + json.forEach((String key, dynamic v) => map[key] = PlayerMessageDTO.fromJson(v)); + } + return map; + } + + // maps a json object with a list of PlayerMessageDTO-objects as value to a dart map + static Map> mapListFromJson(Map json, {bool emptyIsNull, bool growable,}) { + final map = >{}; + if (json != null && json.isNotEmpty) { + json.forEach((String key, dynamic v) { + map[key] = PlayerMessageDTO.listFromJson(v, emptyIsNull: emptyIsNull, growable: growable); + }); + } + return map; + } +} + diff --git a/manager_api/swagger.yaml b/manager_api/swagger.yaml index a28aa3b..e6e57ee 100644 --- a/manager_api/swagger.yaml +++ b/manager_api/swagger.yaml @@ -1018,6 +1018,20 @@ paths: $ref: '#/components/schemas/MenuDTO' security: - bearer: [] + /api/Section/PlayerMessageDTO: + get: + tags: + - Section + operationId: Section_PlayerMessageDTO + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PlayerMessageDTO' + security: + - bearer: [] /api/User: get: tags: @@ -1586,6 +1600,14 @@ components: nullable: true items: $ref: '#/components/schemas/SectionDTO' + PlayerMessageDTO: + type: object + additionalProperties: false + properties: + configChanged: + type: boolean + isDeleted: + type: boolean User: type: object additionalProperties: false diff --git a/manager_api/test/player_message_dto_test.dart b/manager_api/test/player_message_dto_test.dart new file mode 100644 index 0000000..44cd9f8 --- /dev/null +++ b/manager_api/test/player_message_dto_test.dart @@ -0,0 +1,26 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.0 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: lines_longer_than_80_chars + +import 'package:managerapi/api.dart'; +import 'package:test/test.dart'; + +// tests for PlayerMessageDTO +void main() { + final instance = PlayerMessageDTO(); + + group('test PlayerMessageDTO', () { + // bool configChanged + test('to test the property `configChanged`', () async { + // TODO + }); + + + }); + +}