Socket connection and entity service call
This commit is contained in:
parent
be6cd7dbd7
commit
b2bb5f78f6
152
lib/main.dart
152
lib/main.dart
@ -2,12 +2,14 @@ import 'dart:convert';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:web_socket_channel/io.dart';
|
||||||
|
import 'package:web_socket_channel/status.dart' as socketStatus;
|
||||||
|
|
||||||
part 'settings.dart';
|
part 'settings.dart';
|
||||||
|
|
||||||
void main() => runApp(new MyApp());
|
void main() => runApp(new HassClientApp());
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class HassClientApp extends StatelessWidget {
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -44,64 +46,130 @@ class MainPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MainPageState extends State<MainPage> {
|
class _MainPageState extends State<MainPage> {
|
||||||
List _composedEntitiesData = [];
|
List _entitiesData = [];
|
||||||
|
Map _servicesData = {};
|
||||||
String _hassioAPIEndpoint = "";
|
String _hassioAPIEndpoint = "";
|
||||||
String _hassioPassword = "";
|
String _hassioPassword = "";
|
||||||
|
IOWebSocketChannel _hassioChannel;
|
||||||
|
int _entitiesMessageId = 0;
|
||||||
|
int _servicesMessageId = 1;
|
||||||
|
int _servicCallMessageId = 2;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadSettings();
|
_initClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadSettings() async {
|
_initClient() async {
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
setState(() {
|
setState(() {
|
||||||
_hassioAPIEndpoint = "https://" + prefs.getString('hassio-domain') +":" + prefs.getString('hassio-port') + "/api/";
|
_hassioAPIEndpoint = "wss://" + prefs.getString('hassio-domain') +":" + prefs.getString('hassio-port') + "/api/websocket";
|
||||||
_hassioPassword = prefs.getString('hassio-password');
|
_hassioPassword = prefs.getString('hassio-password');
|
||||||
});
|
});
|
||||||
|
_connectSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
_connectSocket() async {
|
||||||
|
_hassioChannel = await IOWebSocketChannel.connect(_hassioAPIEndpoint);
|
||||||
|
_hassioChannel.stream.listen((message) {
|
||||||
|
_handleSocketMessage(message);
|
||||||
|
});
|
||||||
|
debugPrint("Socket connected!");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleSocketMessage(message) {
|
||||||
|
debugPrint("<== Message from Home Assistant:");
|
||||||
|
debugPrint(message);
|
||||||
|
var data = json.decode(message);
|
||||||
|
if (data["type"] == "auth_required") {
|
||||||
|
_sendHassioAuth();
|
||||||
|
} else if (data["type"] == "auth_ok") {
|
||||||
|
debugPrint("Auth done!");
|
||||||
|
_startDataFetching();
|
||||||
|
} else if (data["type"] == "result") {
|
||||||
|
if (data["success"] == true) {
|
||||||
|
if (data["id"] == _entitiesMessageId) {
|
||||||
|
_loadEntities(data["result"]);
|
||||||
|
_sendRawMessage('{"id": $_servicesMessageId, "type": "get_services"}');
|
||||||
|
} else if (data["id"] == _servicesMessageId) {
|
||||||
|
_loadServices(data["result"]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
Handle error here
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_incrementMessageId() {
|
||||||
|
_entitiesMessageId = _servicCallMessageId + 1;
|
||||||
|
_servicesMessageId = _entitiesMessageId + 1;
|
||||||
|
_servicCallMessageId = _servicesMessageId + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendHassioAuth() {
|
||||||
|
_sendRawMessage('{"type": "auth","api_password": "$_hassioPassword"}');
|
||||||
|
}
|
||||||
|
|
||||||
|
_startDataFetching() {
|
||||||
|
_incrementMessageId();
|
||||||
|
_sendRawMessage('{"id": $_entitiesMessageId, "type": "get_states"}');
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendRawMessage(message) {
|
||||||
|
debugPrint("==> Sending to Home Assistant:");
|
||||||
|
debugPrint(message);
|
||||||
|
_hassioChannel.sink.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendServiceCall(String domain, String service, String entityId) {
|
||||||
|
_incrementMessageId();
|
||||||
|
_sendRawMessage('{"id": $_servicCallMessageId, "type": "call_service", "domain": "$domain", "service": "$service", "service_data": {"entity_id": "$entityId"}}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadServices(Map data) {
|
||||||
|
setState(() {
|
||||||
|
_servicesData = Map.from(data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadHassioData() async {
|
void _loadEntities(List data) {
|
||||||
await _loadSettings();
|
Map switchServices = {
|
||||||
http.Response entitiesResponse = await http.get(_hassioAPIEndpoint + "states", headers: {"X-HA-Access": _hassioPassword, "Content-Type": "application/json"});
|
"turn_on": {},
|
||||||
http.Response servicesResponse = await http.get(_hassioAPIEndpoint + "services", headers: {"X-HA-Access": _hassioPassword, "Content-Type": "application/json"});
|
"turn_off": {},
|
||||||
http.Response configResponse = await http.get(_hassioAPIEndpoint + "config", headers: {"X-HA-Access": _hassioPassword, "Content-Type": "application/json"});
|
"toggle": {}
|
||||||
List _entities = json.decode(entitiesResponse.body);
|
};
|
||||||
List _services = json.decode(servicesResponse.body);
|
debugPrint("Getting Home Assistant entities: ${data.length}");
|
||||||
Map _config = json.decode(configResponse.body);
|
data.forEach((entity) {
|
||||||
List result = [];
|
var composedEntity = Map.from(entity);
|
||||||
_entities.forEach((entity) {
|
|
||||||
var composedEntity = Map();
|
|
||||||
composedEntity["entity_id"] = entity["entity_id"];
|
|
||||||
composedEntity["display_name"] = "${entity["attributes"]!=null ? entity["attributes"]["friendly_name"] ?? entity["attributes"]["name"] : "_"}";
|
composedEntity["display_name"] = "${entity["attributes"]!=null ? entity["attributes"]["friendly_name"] ?? entity["attributes"]["name"] : "_"}";
|
||||||
composedEntity["state"] = entity["state"];
|
|
||||||
composedEntity["last_changed"] = entity["last_changed"];
|
|
||||||
String entityDomain = entity["entity_id"].split(".")[0];
|
String entityDomain = entity["entity_id"].split(".")[0];
|
||||||
composedEntity["domain"] = entityDomain;
|
composedEntity["domain"] = entityDomain;
|
||||||
|
|
||||||
_services.forEach((service) {
|
if ((entityDomain == "automation") || (entityDomain == "light") || (entityDomain == "switch") || (entityDomain == "script")) {
|
||||||
if (service["domain"] == entityDomain) {
|
composedEntity["services"] = Map.from(switchServices);
|
||||||
composedEntity["services"] = new Map.from(service["services"]);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
result.add(composedEntity);
|
|
||||||
});
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_composedEntitiesData = result;
|
_entitiesData.add(composedEntity);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildEntityButtons(int i) {
|
Widget buildEntityButtons(int i) {
|
||||||
if (_composedEntitiesData[i]["services"] == null || _composedEntitiesData[i]["services"].length == 0) {
|
if (_entitiesData[i]["services"] == null || _entitiesData[i]["services"].length == 0) {
|
||||||
return new Container(width: 0.0, height: 0.0);
|
return new Container(width: 0.0, height: 0.0);
|
||||||
}
|
}
|
||||||
List<Widget> buttons = [];
|
List<Widget> buttons = [];
|
||||||
_composedEntitiesData[i]["services"].forEach((key, value) {
|
_entitiesData[i]["services"].forEach((key, value) {
|
||||||
buttons.add(new FlatButton(
|
buttons.add(new FlatButton(
|
||||||
child: Text(_composedEntitiesData[i]["domain"] + ".$key"),
|
child: Text(_entitiesData[i]["domain"] + ".$key"),
|
||||||
onPressed: () {/*......*/},
|
onPressed: () {
|
||||||
|
_sendServiceCall(_entitiesData[i]["domain"], key, _entitiesData[i]["entity_id"]);
|
||||||
|
},
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
return ButtonBar(
|
return ButtonBar(
|
||||||
@ -109,16 +177,16 @@ class _MainPageState extends State<MainPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget parseEntity(int i) {
|
Widget buildEntityCard(int i) {
|
||||||
return Card(
|
return Card(
|
||||||
child: new Column(
|
child: new Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: const Icon(Icons.device_hub),
|
leading: const Icon(Icons.device_hub),
|
||||||
subtitle: Text("${_composedEntitiesData[i]["entity_id"]}"),
|
subtitle: Text("${_entitiesData[i]["entity_id"]}"),
|
||||||
trailing: Text("${_composedEntitiesData[i]["state"]}"),
|
trailing: Text("${_entitiesData[i]["state"]}"),
|
||||||
title: Text("${_composedEntitiesData[i]["display_name"]}"),
|
title: Text("${_entitiesData[i]["display_name"]}"),
|
||||||
),
|
),
|
||||||
new ButtonTheme.bar( // make buttons use the appropriate styles for cards
|
new ButtonTheme.bar( // make buttons use the appropriate styles for cards
|
||||||
child: buildEntityButtons(i),
|
child: buildEntityButtons(i),
|
||||||
@ -131,7 +199,7 @@ class _MainPageState extends State<MainPage> {
|
|||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.all(10.0),
|
padding: EdgeInsets.all(10.0),
|
||||||
child: Text("Row ${_composedEntitiesData[i]["entity_id"]}")
|
child: Text("Row ${_entitiesData[i]["entity_id"]}")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,15 +243,21 @@ class _MainPageState extends State<MainPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemCount: _composedEntitiesData.length,
|
itemCount: _entitiesData.length,
|
||||||
itemBuilder: (BuildContext context, int position) {
|
itemBuilder: (BuildContext context, int position) {
|
||||||
return parseEntity(position);
|
return buildEntityCard(position);
|
||||||
}),
|
}),
|
||||||
floatingActionButton: new FloatingActionButton(
|
floatingActionButton: new FloatingActionButton(
|
||||||
onPressed: _loadHassioData,
|
onPressed: _startDataFetching,
|
||||||
tooltip: 'Increment',
|
tooltip: 'Increment',
|
||||||
child: new Icon(Icons.refresh),
|
child: new Icon(Icons.refresh),
|
||||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_hassioChannel.sink.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import 'package:hass_client/main.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(new MyApp());
|
await tester.pumpWidget(new HassClientApp());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
Reference in New Issue
Block a user