HTTP-клиент к /api/v1 с Sanctum-токеном (хранится в secure storage), модели зеркалят Laravel API Resources. Экраны: логин, список устройств с живым статусом, детальная страница устройства (история телеметрии, turn-on/turn-off/set-level с проверкой capability), списки зон и правил автоматизации (пока только чтение — формы создания/редактирования впереди).
81 lines
2.6 KiB
Dart
81 lines
2.6 KiB
Dart
import 'device_type.dart';
|
|
import 'zone.dart';
|
|
|
|
class Device {
|
|
Device({
|
|
required this.id,
|
|
required this.name,
|
|
required this.externalId,
|
|
required this.protocol,
|
|
required this.status,
|
|
this.zone,
|
|
this.deviceType,
|
|
});
|
|
|
|
final int id;
|
|
final String name;
|
|
final String externalId;
|
|
final String protocol;
|
|
final String status; // "online" | "offline" | "unknown"
|
|
final Zone? zone;
|
|
final DeviceType? deviceType;
|
|
|
|
factory Device.fromJson(Map<String, dynamic> json) => Device(
|
|
id: json['id'] as int,
|
|
name: json['name'] as String,
|
|
externalId: json['external_id'] as String,
|
|
protocol: json['protocol'] as String,
|
|
status: json['status'] as String? ?? 'unknown',
|
|
zone: json['zone'] is Map ? Zone.fromJson(json['zone'] as Map<String, dynamic>) : null,
|
|
deviceType: json['device_type'] is Map
|
|
? DeviceType.fromJson(json['device_type'] as Map<String, dynamic>)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
/// The extra data returned only by `GET /devices/{id}`: Device Shadow
|
|
/// snapshot (Redis) plus recent telemetry readings (ClickHouse, sensors only).
|
|
class DeviceDetail {
|
|
DeviceDetail({
|
|
required this.device,
|
|
required this.lastSeen,
|
|
required this.desiredState,
|
|
required this.reportedState,
|
|
required this.telemetry,
|
|
});
|
|
|
|
final Device device;
|
|
final DateTime? lastSeen;
|
|
final Map<String, dynamic> desiredState;
|
|
final Map<String, dynamic> reportedState;
|
|
final List<TelemetryReading> telemetry;
|
|
|
|
factory DeviceDetail.fromJson(Map<String, dynamic> json) {
|
|
final shadow = json['shadow'] as Map<String, dynamic>;
|
|
|
|
return DeviceDetail(
|
|
device: Device.fromJson(json['data'] as Map<String, dynamic>),
|
|
lastSeen: shadow['last_seen'] != null ? DateTime.tryParse(shadow['last_seen'] as String) : null,
|
|
desiredState: Map<String, dynamic>.from(shadow['desired_state'] as Map? ?? const {}),
|
|
reportedState: Map<String, dynamic>.from(shadow['reported_state'] as Map? ?? const {}),
|
|
telemetry: (json['telemetry'] as List? ?? const [])
|
|
.map((e) => TelemetryReading.fromJson(e as Map<String, dynamic>))
|
|
.toList(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TelemetryReading {
|
|
TelemetryReading({required this.sensorType, required this.value, required this.recordedAt});
|
|
|
|
final String sensorType;
|
|
final double value;
|
|
final String recordedAt;
|
|
|
|
factory TelemetryReading.fromJson(Map<String, dynamic> json) => TelemetryReading(
|
|
sensorType: json['sensor_type'] as String,
|
|
value: (json['value'] as num).toDouble(),
|
|
recordedAt: json['recorded_at'] as String,
|
|
);
|
|
}
|