Add mqtt listener + update service generation

This commit is contained in:
Thomas Fransolet 2021-07-25 01:50:31 +02:00
parent 9657240cb0
commit ff28b7945a
14 changed files with 415 additions and 17 deletions

View File

@ -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<MqttReceivedMessage<MqttMessage>> 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<MqttServerClient> connect(dynamic tabletAppContext) async {
Future<MqttServerClient> 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<void> 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
);
}
}
}

View File

@ -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<ConfigViewWidget> {
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;

View File

@ -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<MainViewWidget> {
SystemChrome.setEnabledSystemUIOverlays([]);
Size size = MediaQuery.of(context).size;
if (!MQTTHelper.instance.isInstantiated)
MQTTHelper.instance.connect(appContext);
if(sectionSelected != null) {
var elementToShow;
switch (sectionSelected.type) {

View File

@ -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<MyApp> {
@override
Widget build(BuildContext context) {
if (widget.tabletAppContext != null)
MQTTHelper.instance.connect(widget.tabletAppContext);
return ChangeNotifierProvider<AppContext>(
create: (_) => AppContext(widget.tabletAppContext),
child: MaterialApp(

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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<OAuth>('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)

View File

@ -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';

View File

@ -716,6 +716,58 @@ class SectionApi {
return Future<WebDTO>.value(null);
}
/// Performs an HTTP 'GET /api/Section/PlayerMessageDTO' operation and returns the [Response].
Future<Response> sectionPlayerMessageDTOWithHttpInfo() async {
final path = r'/api/Section/PlayerMessageDTO';
Object postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
final contentTypes = <String>[];
final nullableContentType = contentTypes.isNotEmpty ? contentTypes[0] : null;
final authNames = <String>['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<PlayerMessageDTO> 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<PlayerMessageDTO>.value(null);
}
/// Performs an HTTP 'PUT /api/Section' operation and returns the [Response].
/// Parameters:
///

View File

@ -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':

View File

@ -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<String, dynamic> toJson() {
final json = <String, dynamic>{};
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<String, dynamic> json) => json == null
? null
: PlayerMessageDTO(
configChanged: json[r'configChanged'],
isDeleted: json[r'isDeleted'],
);
static List<PlayerMessageDTO> listFromJson(List<dynamic> json, {bool emptyIsNull, bool growable,}) =>
json == null || json.isEmpty
? true == emptyIsNull ? null : <PlayerMessageDTO>[]
: json.map((v) => PlayerMessageDTO.fromJson(v)).toList(growable: true == growable);
static Map<String, PlayerMessageDTO> mapFromJson(Map<String, dynamic> json) {
final map = <String, PlayerMessageDTO>{};
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<String, List<PlayerMessageDTO>> mapListFromJson(Map<String, dynamic> json, {bool emptyIsNull, bool growable,}) {
final map = <String, List<PlayerMessageDTO>>{};
if (json != null && json.isNotEmpty) {
json.forEach((String key, dynamic v) {
map[key] = PlayerMessageDTO.listFromJson(v, emptyIsNull: emptyIsNull, growable: growable);
});
}
return map;
}
}

View File

@ -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

View File

@ -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
});
});
}