Skip to content

Commit 554ca92

Browse files
committed
Implemented MovesenseDeviceInfo as DTO
1 parent 1fe8eba commit 554ca92

5 files changed

Lines changed: 164 additions & 20 deletions

File tree

packages/movesense_flutter/example/lib/main.dart

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class MovesenseHomePage extends StatefulWidget {
2323
}
2424

2525
class MovesenseHomePageState extends State<MovesenseHomePage> {
26+
// My device address: 0C:8C:DC:1B:23:BF, serial 233830000816
2627
// Replace with your Movesense device address.
2728
final MovesenseDevice device = MovesenseDevice(address: '0C:8C:DC:1B:23:BF');
2829
bool isSampling = false;
@@ -85,18 +86,23 @@ class MovesenseHomePageState extends State<MovesenseHomePage> {
8586

8687
void onButtonPressed() {
8788
setState(() {
89+
// Movesense().devices.listen((device) {
90+
// debugPrint('Discovered device: ${device.name} [${device.address}]');
91+
// });
92+
// Movesense().scan();
8893
if (!device.isConnected) {
8994
device.connect();
9095
} else {
91-
if (!isSampling) {
92-
hrSubscription = device.heartRate.listen((hr) {
93-
debugPrint('Heart Rate: $hr');
94-
});
95-
isSampling = true;
96-
} else {
97-
hrSubscription?.cancel();
98-
isSampling = false;
99-
}
96+
device.getDeviceInfo();
97+
// // if (!isSampling) {
98+
// // hrSubscription = device.heartRate.listen((hr) {
99+
// // debugPrint('Heart Rate: $hr');
100+
// // });
101+
// // isSampling = true;
102+
// // } else {
103+
// // hrSubscription?.cancel();
104+
// // isSampling = false;
105+
// // }
100106
}
101107
});
102108
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
part of 'movesense_flutter.dart';
2+
3+
/// Device info returned from the Movesense `/Info` endpoint.
4+
class MovesenseDeviceInfo {
5+
final String manufacturerName;
6+
final String? brandName;
7+
final String productName;
8+
final String variant;
9+
final String design;
10+
final String hwCompatibilityId;
11+
final String serial;
12+
final String pcbaSerial;
13+
final String sw;
14+
final String hw;
15+
final String? additionalVersionInfo;
16+
final List<MovesenseAddressInfo> addressInfo;
17+
final String apiLevel;
18+
19+
const MovesenseDeviceInfo({
20+
required this.manufacturerName,
21+
required this.brandName,
22+
required this.productName,
23+
required this.variant,
24+
required this.design,
25+
required this.hwCompatibilityId,
26+
required this.serial,
27+
required this.pcbaSerial,
28+
required this.sw,
29+
required this.hw,
30+
required this.additionalVersionInfo,
31+
required this.addressInfo,
32+
required this.apiLevel,
33+
});
34+
35+
/// Build from the raw JSON string returned by the device.
36+
factory MovesenseDeviceInfo.fromJsonString(String jsonString) {
37+
final dynamic decoded = json.decode(jsonString);
38+
if (decoded is! Map<String, dynamic>) {
39+
throw const FormatException('Device info JSON must be an object');
40+
}
41+
final dynamic content = decoded['Content'];
42+
if (content is! Map<String, dynamic>) {
43+
throw const FormatException('Device info JSON missing Content object');
44+
}
45+
return MovesenseDeviceInfo.fromMap(content);
46+
}
47+
48+
/// Build from a JSON map.
49+
factory MovesenseDeviceInfo.fromMap(Map<String, dynamic> map) {
50+
final dynamic addresses = map['addressInfo'];
51+
if (addresses is! List) {
52+
throw const FormatException('Device info missing addressInfo list');
53+
}
54+
55+
return MovesenseDeviceInfo(
56+
manufacturerName: map['manufacturerName'] as String? ?? '',
57+
brandName: map['brandName'] as String?,
58+
productName: map['productName'] as String? ?? '',
59+
variant: map['variant'] as String? ?? '',
60+
design: map['design'] as String? ?? '',
61+
hwCompatibilityId: map['hwCompatibilityId'] as String? ?? '',
62+
serial: map['serial'] as String? ?? '',
63+
pcbaSerial: map['pcbaSerial'] as String? ?? '',
64+
sw: map['sw'] as String? ?? '',
65+
hw: map['hw'] as String? ?? '',
66+
additionalVersionInfo: map['additionalVersionInfo'] as String?,
67+
addressInfo: addresses
68+
.map(
69+
(entry) => MovesenseAddressInfo.fromMap(
70+
entry is Map<String, dynamic> ? entry : <String, dynamic>{},
71+
),
72+
)
73+
.toList(growable: false),
74+
apiLevel: map['apiLevel'] as String? ?? '',
75+
);
76+
}
77+
78+
Map<String, dynamic> toMap() => <String, dynamic>{
79+
'manufacturerName': manufacturerName,
80+
'brandName': brandName,
81+
'productName': productName,
82+
'variant': variant,
83+
'design': design,
84+
'hwCompatibilityId': hwCompatibilityId,
85+
'serial': serial,
86+
'pcbaSerial': pcbaSerial,
87+
'sw': sw,
88+
'hw': hw,
89+
'additionalVersionInfo': additionalVersionInfo,
90+
'addressInfo': addressInfo.map((a) => a.toMap()).toList(growable: false),
91+
'apiLevel': apiLevel,
92+
};
93+
94+
/// Serialize to JSON string with the original Content wrapper.
95+
String toJsonString() => json.encode(<String, dynamic>{'Content': toMap()});
96+
}
97+
98+
/// Address info entry in the Movesense device info.
99+
class MovesenseAddressInfo {
100+
final String name;
101+
final String address;
102+
103+
const MovesenseAddressInfo({required this.name, required this.address});
104+
105+
factory MovesenseAddressInfo.fromMap(Map<String, dynamic> map) {
106+
return MovesenseAddressInfo(
107+
name: map['name'] as String? ?? '',
108+
address: map['address'] as String? ?? '',
109+
);
110+
}
111+
112+
Map<String, dynamic> toMap() => <String, dynamic>{
113+
'name': name,
114+
'address': address,
115+
};
116+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"Content": {
3+
"manufacturerName": "Movesense Ltd",
4+
"brandName": null,
5+
"productName": "Movesense",
6+
"variant": "Avocado",
7+
"design": "black",
8+
"hwCompatibilityId": "C",
9+
"serial": "233830000816",
10+
"pcbaSerial": "80413336500828200000100022666H4233",
11+
"sw": "2.1.5",
12+
"hw": "H4",
13+
"additionalVersionInfo": null,
14+
"addressInfo": [
15+
{
16+
"name": "BLE",
17+
"address": "0C-8C-DC-1B-23-BF"
18+
},
19+
{
20+
"name": "DFU-BLE",
21+
"address": "E9-AC-4C-25-9A-65"
22+
}
23+
],
24+
"apiLevel": "1"
25+
}
26+
}

packages/movesense_flutter/lib/movesense_device.dart

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,12 @@ class MovesenseDevice {
4747
bool get isConnected => serial != null;
4848
// bool get isConnected => status == DeviceConnectionStatus.connected;
4949

50-
// TODO: make a device info class instead of using a Map.
5150
/// The latest device info for the connected Movesense device.
5251
/// Only available after device is connected.
5352
/// See https://www.movesense.com/docs/esw/api_reference/#info
54-
Map<String, dynamic>? deviceInfo;
53+
MovesenseDeviceInfo? deviceInfo;
5554

56-
/// Connect to the Movesense device using the [address].
55+
/// Connect to the Movesense device using the [address] specified.
5756
/// If the address is not set, an exception is thrown.
5857
Future<void> connect() async {
5958
if (!canConnect()) {
@@ -89,26 +88,22 @@ class MovesenseDevice {
8988
/// See https://www.movesense.com/docs/esw/api_reference/#info
9089
/// Returns a Future that completes with a Map containing the device info,
9190
/// or null if the device is not connected.
92-
Future<Map<String, dynamic>?> getDeviceInfo() async {
91+
Future<MovesenseDeviceInfo?> getDeviceInfo() async {
9392
// fast out if not connected
9493
if (!isConnected) return null;
9594

96-
var completer = Completer<Map<String, dynamic>?>();
95+
var completer = Completer<MovesenseDeviceInfo?>();
9796

9897
Mds.get(
9998
Mds.createRequestUri(serial!, "/Info"),
10099
"{}",
101100
// onSuccess
102101
((info, statusCode) {
103-
debugPrint('$runtimeType - Movesense Device Info:\n$info');
104-
final dataContent = json.decode(info);
105-
deviceInfo = dataContent["Content"] as Map<String, dynamic>;
106-
String hw = (deviceInfo!["hw"] as String).toUpperCase();
107-
debugPrint('$runtimeType - HW: $hw');
102+
deviceInfo = MovesenseDeviceInfo.fromJsonString(info);
108103

109104
// Try to figure out the type of device based on the "hw" property
110105
// H3 is "HR+", H4 is "HR2", A1 is "MD"
111-
deviceType = switch (hw) {
106+
deviceType = switch (deviceInfo?.hw.toUpperCase()) {
112107
'A1' => MovesenseDeviceType.MD,
113108
'H3' => MovesenseDeviceType.HR_PLUS,
114109
'H4' => MovesenseDeviceType.HR2,

packages/movesense_flutter/lib/movesense_flutter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
66
import 'package:mdsflutter/Mds.dart';
77

88
part 'movesense_device.dart';
9+
part 'device_info.dart';
910

1011
class Movesense {
1112
static final Movesense _instance = Movesense._();

0 commit comments

Comments
 (0)