Socket connection and entity service call

This commit is contained in:
estevez 2018-09-12 00:32:04 +03:00
parent be6cd7dbd7
commit b2bb5f78f6
2 changed files with 116 additions and 42 deletions

View File

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

View File

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