2023-10-05 12:54:11 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
|
2023-10-11 07:57:37 +00:00
|
|
|
import 'constants.dart';
|
|
|
|
|
2023-10-05 12:54:11 +00:00
|
|
|
class Category {
|
2023-10-09 09:35:24 +00:00
|
|
|
Category(this.name) : devices = [];
|
|
|
|
|
2023-10-05 12:54:11 +00:00
|
|
|
final String name;
|
|
|
|
List<Device> devices;
|
|
|
|
|
2023-10-11 07:57:37 +00:00
|
|
|
static Future<List<Category>> fetch() async {
|
|
|
|
final response = await http.get(Uri.parse("${Constants.BASE_URL}/devices"));
|
2023-10-05 12:54:11 +00:00
|
|
|
|
2023-10-06 07:15:17 +00:00
|
|
|
if (response.statusCode != 200) {
|
|
|
|
throw Exception("Failed to fetch devices");
|
|
|
|
}
|
|
|
|
|
2023-10-09 09:35:24 +00:00
|
|
|
final List<Category> categories = [];
|
2023-10-09 18:12:34 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
final Map<String, List<dynamic>> json =
|
|
|
|
Map.castFrom(jsonDecode(jsonDecode(response.body)));
|
2023-10-06 06:50:31 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
for (MapEntry<String, List<dynamic>> category_entry in json.entries) {
|
|
|
|
final Category category = Category(category_entry.key);
|
2023-10-06 06:50:31 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
for (dynamic device_info_dyn in category_entry.value) {
|
|
|
|
final Map<String, dynamic> device_info = device_info_dyn;
|
2023-10-09 18:12:34 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
final String device_id = device_info['id'];
|
|
|
|
final String? device_descriptor = device_info['desc'];
|
|
|
|
final bool power_control = device_info['toggle'];
|
2023-10-06 07:15:17 +00:00
|
|
|
|
2023-10-11 07:57:37 +00:00
|
|
|
final response = await http
|
|
|
|
.get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id"));
|
2023-10-06 07:15:17 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
if (response.statusCode != 200) {
|
|
|
|
throw Exception("Failed to fetch plug_state for $device_id");
|
|
|
|
}
|
2023-10-05 12:54:11 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
final Map<String, dynamic> device_state =
|
|
|
|
Map.castFrom(jsonDecode(jsonDecode(response.body)));
|
2023-10-10 09:05:51 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
final Device device = Device(
|
|
|
|
device_id,
|
|
|
|
device_descriptor,
|
|
|
|
device_state["led"],
|
|
|
|
device_state["power"],
|
|
|
|
device_state["power_draw"],
|
|
|
|
power_control && device_state["power_draw"] < 15,
|
|
|
|
);
|
2023-10-10 09:05:51 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
category.devices.add(device);
|
|
|
|
}
|
2023-10-10 09:05:51 +00:00
|
|
|
|
2023-10-10 12:06:26 +00:00
|
|
|
categories.add(category);
|
|
|
|
}
|
2023-10-06 07:15:17 +00:00
|
|
|
|
|
|
|
return categories;
|
2023-10-05 12:54:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-06 06:50:31 +00:00
|
|
|
class CategoryWidget extends StatelessWidget {
|
|
|
|
Category category;
|
|
|
|
|
|
|
|
CategoryWidget({super.key, required this.category});
|
|
|
|
|
2023-10-11 09:16:12 +00:00
|
|
|
String Name() {
|
|
|
|
var s = category.name;
|
2023-10-10 12:06:26 +00:00
|
|
|
return "${s[0].toUpperCase()}${s.substring(1)}";
|
|
|
|
}
|
|
|
|
|
2023-10-06 06:50:31 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
var list = category.devices
|
|
|
|
.map((device) => TableRow(children: [
|
2023-10-10 12:06:26 +00:00
|
|
|
Text(
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
device.device_descriptor ?? device.device_id),
|
2023-10-11 07:57:37 +00:00
|
|
|
DeviceLed(
|
|
|
|
device_id: device.device_id, led_state: device.led_state),
|
|
|
|
DevicePower(
|
|
|
|
device_id: device.device_id,
|
|
|
|
power_state: device.power_state,
|
|
|
|
power_control: device.power_control),
|
2023-10-10 12:06:26 +00:00
|
|
|
Text(textAlign: TextAlign.center, "${device.power_draw} W")
|
2023-10-06 06:50:31 +00:00
|
|
|
]))
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
list.insert(
|
|
|
|
0,
|
2023-10-10 12:06:26 +00:00
|
|
|
const TableRow(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.blueGrey,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
Text(
|
|
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
"Name"),
|
|
|
|
Text(
|
|
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
"LED"),
|
|
|
|
Text(
|
|
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
"Power"),
|
|
|
|
Text(
|
|
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
"Power Draw"),
|
|
|
|
]));
|
|
|
|
|
2023-10-11 09:16:12 +00:00
|
|
|
return Table(
|
|
|
|
border: TableBorder.all(),
|
|
|
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
|
|
|
children: list);
|
2023-10-06 06:50:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-05 12:54:11 +00:00
|
|
|
class Device {
|
2023-10-06 06:50:31 +00:00
|
|
|
final String device_id;
|
|
|
|
String? device_descriptor;
|
2023-10-05 12:54:11 +00:00
|
|
|
bool led_state;
|
|
|
|
bool power_state;
|
|
|
|
double power_draw;
|
2023-10-06 07:15:17 +00:00
|
|
|
bool power_control;
|
2023-10-05 12:54:11 +00:00
|
|
|
|
2023-10-06 06:50:31 +00:00
|
|
|
Device(this.device_id, this.device_descriptor, this.led_state,
|
2023-10-06 07:15:17 +00:00
|
|
|
this.power_state, this.power_draw, this.power_control);
|
2023-10-05 12:54:11 +00:00
|
|
|
}
|
2023-10-11 07:57:37 +00:00
|
|
|
|
|
|
|
class DeviceLed extends StatefulWidget {
|
|
|
|
final String device_id;
|
|
|
|
final bool led_state;
|
|
|
|
|
|
|
|
DeviceLed({super.key, required this.device_id, required this.led_state});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<DeviceLed> createState() => DeviceLedState(device_id, led_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
class DeviceLedState extends State<DeviceLed> {
|
|
|
|
final String device_id;
|
|
|
|
bool led_state;
|
|
|
|
String _led_state_info;
|
|
|
|
|
|
|
|
DeviceLedState(this.device_id, this.led_state)
|
|
|
|
: this._led_state_info = led_state ? "On" : "Off";
|
|
|
|
|
|
|
|
void _toggle_led_state() {
|
|
|
|
String target_state;
|
|
|
|
|
|
|
|
if (led_state) {
|
|
|
|
target_state = "off";
|
|
|
|
} else {
|
|
|
|
target_state = "on";
|
|
|
|
}
|
|
|
|
|
|
|
|
change_plug_state(device_id, "led", target_state).then((device_state) {
|
|
|
|
led_state = device_state["led"];
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
_led_state_info = led_state ? "On" : "Off";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return TextButton(
|
|
|
|
onPressed: _toggle_led_state,
|
|
|
|
child: Text(textAlign: TextAlign.center, _led_state_info));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class DevicePower extends StatefulWidget {
|
|
|
|
final String device_id;
|
|
|
|
final bool power_state;
|
|
|
|
final bool power_control;
|
|
|
|
|
|
|
|
DevicePower(
|
|
|
|
{super.key,
|
|
|
|
required this.device_id,
|
|
|
|
required this.power_state,
|
|
|
|
required this.power_control});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<DevicePower> createState() =>
|
|
|
|
DevicePowerState(device_id, power_state, power_control);
|
|
|
|
}
|
|
|
|
|
|
|
|
class DevicePowerState extends State<DevicePower> {
|
|
|
|
final String device_id;
|
|
|
|
bool power_state;
|
|
|
|
String _power_state_info;
|
|
|
|
bool power_control;
|
|
|
|
|
|
|
|
DevicePowerState(this.device_id, this.power_state, this.power_control)
|
|
|
|
: this._power_state_info = power_state ? "On" : "Off";
|
|
|
|
|
|
|
|
void _toggle_power_state() {
|
|
|
|
String target_state;
|
|
|
|
|
|
|
|
if (power_state) {
|
|
|
|
target_state = "off";
|
|
|
|
} else {
|
|
|
|
target_state = "on";
|
|
|
|
}
|
|
|
|
|
|
|
|
change_plug_state(device_id, "power", target_state).then((device_state) {
|
|
|
|
power_state = device_state["power"];
|
|
|
|
|
|
|
|
power_control = device_state["power_draw"] < 15;
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
_power_state_info = power_state ? "On" : "Off";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
if (power_control) {
|
|
|
|
return TextButton(
|
|
|
|
onPressed: _toggle_power_state,
|
|
|
|
child: Text(textAlign: TextAlign.center, _power_state_info));
|
|
|
|
} else {
|
|
|
|
return Text(_power_state_info, textAlign: TextAlign.center);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Map<String, dynamic>> change_plug_state(
|
|
|
|
String device_id, String module, String state) async {
|
|
|
|
var response = await http.post(
|
|
|
|
Uri.parse("${Constants.BASE_URL}/plug/$device_id/${module}_$state"));
|
|
|
|
|
|
|
|
if (response.statusCode != 200) {
|
|
|
|
throw Exception("Failed to post new state");
|
|
|
|
}
|
|
|
|
|
|
|
|
response =
|
|
|
|
await http.get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id"));
|
|
|
|
|
|
|
|
if (response.statusCode != 200) {
|
|
|
|
throw Exception("Failed to fetch plug_state for $device_id");
|
|
|
|
}
|
|
|
|
|
|
|
|
return Map.castFrom(jsonDecode(jsonDecode(response.body)));
|
|
|
|
}
|