This commit is contained in:
Yegor Vialov 2018-10-27 00:54:05 +03:00
parent 54979b583b
commit 8fb0d61a84
4 changed files with 246 additions and 137 deletions

View File

@ -4,6 +4,7 @@ class HomeAssistant {
String _webSocketAPIEndpoint; String _webSocketAPIEndpoint;
String _password; String _password;
String _authType; String _authType;
bool _useLovelace;
IOWebSocketChannel _hassioChannel; IOWebSocketChannel _hassioChannel;
SendMessageQueue _messageQueue; SendMessageQueue _messageQueue;
@ -14,14 +15,18 @@ class HomeAssistant {
int _subscriptionMessageId = 0; int _subscriptionMessageId = 0;
int _configMessageId = 0; int _configMessageId = 0;
int _userInfoMessageId = 0; int _userInfoMessageId = 0;
int _lovelaceMessageId = 0;
EntityCollection entities; EntityCollection entities;
GroupBasedUI ui; HomeAssistantUI ui;
Map _instanceConfig = {}; Map _instanceConfig = {};
String _userName; String _userName;
Map _rawLovelaceData;
Completer _fetchCompleter; Completer _fetchCompleter;
Completer _statesCompleter; Completer _statesCompleter;
Completer _servicesCompleter; Completer _servicesCompleter;
Completer _lovelaceCompleter;
Completer _configCompleter; Completer _configCompleter;
Completer _connectionCompleter; Completer _connectionCompleter;
Completer _userInfoCompleter; Completer _userInfoCompleter;
@ -45,10 +50,12 @@ class HomeAssistant {
_messageQueue = SendMessageQueue(messageExpirationTime); _messageQueue = SendMessageQueue(messageExpirationTime);
} }
void updateConnectionSettings(String url, String password, String authType) { void updateSettings(String url, String password, String authType, bool useLovelace) {
_webSocketAPIEndpoint = url; _webSocketAPIEndpoint = url;
_password = password; _password = password;
_authType = authType; _authType = authType;
_useLovelace = useLovelace;
TheLogger.log("Debug", "Use lovelace is $_useLovelace");
} }
Future fetch() { Future fetch() {
@ -150,11 +157,15 @@ class HomeAssistant {
_getData() async { _getData() async {
List<Future> futures = []; List<Future> futures = [];
futures.add(_getStates()); futures.add(_getStates());
if (_useLovelace) {
futures.add(_getLovelace());
}
futures.add(_getConfig()); futures.add(_getConfig());
futures.add(_getServices()); futures.add(_getServices());
futures.add(_getUserInfo()); futures.add(_getUserInfo());
try { try {
await Future.wait(futures); await Future.wait(futures);
_createUI();
_completeFetching(null); _completeFetching(null);
} catch (error) { } catch (error) {
_completeFetching(error); _completeFetching(error);
@ -202,6 +213,8 @@ class HomeAssistant {
_parseConfig(data); _parseConfig(data);
} else if (data["id"] == _statesMessageId) { } else if (data["id"] == _statesMessageId) {
_parseEntities(data); _parseEntities(data);
} else if (data["id"] == _lovelaceMessageId) {
_handleLovelace(data);
} else if (data["id"] == _servicesMessageId) { } else if (data["id"] == _servicesMessageId) {
_parseServices(data); _parseServices(data);
} else if (data["id"] == _userInfoMessageId) { } else if (data["id"] == _userInfoMessageId) {
@ -247,6 +260,15 @@ class HomeAssistant {
return _statesCompleter.future; return _statesCompleter.future;
} }
Future _getLovelace() {
_lovelaceCompleter = new Completer();
_incrementMessageId();
_lovelaceMessageId = _currentMessageId;
_sendMessageRaw('{"id": $_lovelaceMessageId, "type": "lovelace/config"}', false);
return _lovelaceCompleter.future;
}
Future _getUserInfo() { Future _getUserInfo() {
_userInfoCompleter = new Completer(); _userInfoCompleter = new Completer();
_incrementMessageId(); _incrementMessageId();
@ -336,28 +358,66 @@ class HomeAssistant {
void _parseServices(response) { void _parseServices(response) {
_servicesCompleter.complete(); _servicesCompleter.complete();
/*if (response["success"] == false) { }
_servicesCompleter.completeError({"errorCode": 4, "errorMessage": response["error"]["message"]});
return; void _handleLovelace(response) {
if (response["success"] == true) {
_rawLovelaceData = response["result"];
} else {
_rawLovelaceData = null;
} }
try { _lovelaceCompleter.complete();
Map data = response["result"]; }
Map result = {};
TheLogger.log("Debug","Parsing ${data.length} Home Assistant service domains"); void _parseLovelace() {
data.forEach((domain, services) { ui = HomeAssistantUI();
result[domain] = Map.from(services); TheLogger.log("debug","Parsing lovelace config");
services.forEach((serviceName, serviceData) { TheLogger.log("debug","--Title: ${_rawLovelaceData["title"]}");
if (_entitiesData.isExist("$domain.$serviceName")) { int viewCounter = 0;
result[domain].remove(serviceName); TheLogger.log("debug","--Views count: ${_rawLovelaceData['views'].length}");
_rawLovelaceData["views"].forEach((rawView){
TheLogger.log("debug","----view id: ${rawView['id']}");
HAView view = HAView(
count: viewCounter,
id: rawView['id'],
name: rawView['title'],
iconName: rawView['icon']
);
view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? []));
ui.views.add(
view
);
viewCounter += 1;
});
}
List<HACard> _createLovelaceCards(List rawCards) {
List<HACard> result = [];
rawCards.forEach((rawCard){
if (rawCard["cards"] != null) {
TheLogger.log("debug","------card: ${rawCard['type']} has child cards");
result.addAll(_createLovelaceCards(rawCard["cards"]));
} else {
TheLogger.log("debug","------card: ${rawCard['type']}");
HACard card = HACard(
id: "card",
name: rawCard["title"]
);
rawCard["entities"]?.forEach((rawEntity) {
if (rawEntity is String) {
if (entities.isExist(rawEntity)) {
card.entities.add(entities.get(rawEntity));
}
} else {
if (entities.isExist(rawEntity["entity"])) {
card.entities.add(entities.get(rawEntity["entity"]));
}
} }
}); });
}); result.add(card);
_servicesData = result; }
_servicesCompleter.complete(); });
} catch (e) { return result;
TheLogger.log("Error","Error parsing services. But they are not used :-)");
_servicesCompleter.complete();
}*/
} }
void _parseEntities(response) async { void _parseEntities(response) async {
@ -366,80 +426,46 @@ class HomeAssistant {
return; return;
} }
entities.parse(response["result"]); entities.parse(response["result"]);
ui = GroupBasedUI();
int viewCounter = 0;
//TODO add default_view
if (!entities.hasDefaultView) {
TheLogger.log("Debug","--Default view");
HACView view = HACView(
count: viewCounter,
id: "group.default_view",
name: "Home"
);
_createView(view, entities.filterEntitiesForDefaultView(), viewCounter);
ui.views.add(
view
);
viewCounter+=1;
}
entities.viewEntities.forEach((viewEntity) {
TheLogger.log("Debug","--View: ${viewEntity.entityId}");
HACView view = HACView(
count: viewCounter,
id: viewEntity.entityId,
name: viewEntity.displayName
);
view.linkedEntity = viewEntity;
_createView(view, viewEntity.childEntities, viewCounter);
ui.views.add(
view
);
viewCounter += 1;
});
_statesCompleter.complete(); _statesCompleter.complete();
} }
void _createView(HACView view, List<Entity> childEntities, int viewCounter) { void _createUI() {
List<HACCard> autoGeneratedCards = []; if ((_useLovelace) && (_rawLovelaceData != null)) {
childEntities.forEach((entity) { _parseLovelace();
if (entity.isBadge) { } else {
view.badges.add(entity); ui = HomeAssistantUI();
TheLogger.log("Debug","----Badge: ${entity.entityId}"); int viewCounter = 0;
} else { if (!entities.hasDefaultView) {
if (!entity.isGroup) { TheLogger.log("Debug", "--Default view");
String groupIdToAdd = "${entity.domain}.${entity.domain}$viewCounter"; HAView view = HAView(
if (autoGeneratedCards.every((HACCard card) => card.id != groupIdToAdd )) { count: viewCounter,
HACCard card = HACCard( id: "group.default_view",
id: groupIdToAdd, name: "Home",
name: entity.domain childEntities: entities.filterEntitiesForDefaultView()
); );
TheLogger.log("Debug","----Creating card: $groupIdToAdd"); ui.views.add(
card.entities.add(entity); view
autoGeneratedCards.add(card); );
} else { viewCounter += 1;
autoGeneratedCards.firstWhere((card) => card.id == groupIdToAdd).entities.add(entity);
}
} else {
TheLogger.log("Debug","----Card: ${entity.entityId}");
HACCard card = HACCard(
name: entity.displayName,
id: entity.entityId,
linkedEntity: entity
);
card.entities.addAll(entity.childEntities);
view.cards.add(card);
}
} }
}); entities.viewEntities.forEach((viewEntity) {
view.cards.addAll(autoGeneratedCards); TheLogger.log("Debug", "--View: ${viewEntity.entityId}");
HAView view = HAView(
count: viewCounter,
id: viewEntity.entityId,
name: viewEntity.displayName,
childEntities: viewEntity.childEntities
);
view.linkedEntity = viewEntity;
ui.views.add(
view
);
viewCounter += 1;
});
}
} }
Widget buildViews(BuildContext context) { Widget buildViews(BuildContext context, bool lovelace) {
//return _viewBuilder.buildWidget(context);
return ui.build(context); return ui.build(context);
} }

View File

@ -24,7 +24,7 @@ part 'entity.page.dart';
part 'utils.class.dart'; part 'utils.class.dart';
part 'mdi.class.dart'; part 'mdi.class.dart';
part 'entity_collection.class.dart'; part 'entity_collection.class.dart';
part 'group_based_ui.dart'; part 'ui.dart';
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
const String appName = "HA Client"; const String appName = "HA Client";
@ -62,7 +62,7 @@ class HAClientApp extends StatelessWidget {
initialRoute: "/", initialRoute: "/",
routes: { routes: {
"/": (context) => MainPage(title: 'HA Client'), "/": (context) => MainPage(title: 'HA Client'),
"/connection-settings": (context) => ConnectionSettingsPage(title: "Connection Settings"), "/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
"/log-view": (context) => LogViewPage(title: "Log") "/log-view": (context) => LogViewPage(title: "Log")
}, },
); );
@ -96,6 +96,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
int _isLoading = 1; int _isLoading = 1;
bool _settingsLoaded = false; bool _settingsLoaded = false;
bool _accountMenuExpanded = false; bool _accountMenuExpanded = false;
bool _useLovelaceUI;
@override @override
void initState() { void initState() {
@ -145,6 +146,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port"; homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port";
_password = prefs.getString('hassio-password'); _password = prefs.getString('hassio-password');
_authType = prefs.getString('hassio-auth-type'); _authType = prefs.getString('hassio-auth-type');
_useLovelaceUI = prefs.getBool('use-lovelace') ?? false;
if ((domain == null) || (port == null) || (_password == null) || if ((domain == null) || (port == null) || (_password == null) ||
(domain.length == 0) || (port.length == 0) || (_password.length == 0)) { (domain.length == 0) || (port.length == 0) || (_password.length == 0)) {
throw("Check connection settings"); throw("Check connection settings");
@ -195,7 +197,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
_refreshData() async { _refreshData() async {
_homeAssistant.updateConnectionSettings(_webSocketApiEndpoint, _password, _authType); _homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI);
setState(() { setState(() {
_hideErrorSnackBar(); _hideErrorSnackBar();
_isLoading = 1; _isLoading = 1;
@ -239,33 +241,36 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
List<Tab> buildUIViewTabs() { List<Tab> buildUIViewTabs() {
List<Tab> result = []; List<Tab> result = [];
if (_homeAssistant.ui.views.isNotEmpty) {
_homeAssistant.ui.views.forEach((HACView view) { if (_homeAssistant.ui.views.isNotEmpty) {
if (view.linkedEntity == null) { _homeAssistant.ui.views.forEach((HAView view) {
result.add( if (view.linkedEntity == null) {
Tab( result.add(
icon: Tab(
Icon( icon:
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"), Icon(
size: 24.0, MaterialDesignIcons.createIconDataFromIconName(
) view.iconName ?? "mdi:home-assistant"),
) size: 24.0,
); )
} else { )
result.add( );
Tab( } else {
icon: MaterialDesignIcons.createIconWidgetFromEntityData( result.add(
view.linkedEntity, 24.0, null) ?? Tab(
Icon( icon: MaterialDesignIcons.createIconWidgetFromEntityData(
MaterialDesignIcons.createIconDataFromIconName( view.linkedEntity, 24.0, null) ??
"mdi:home-assistant"), Icon(
size: 24.0, MaterialDesignIcons.createIconDataFromIconName(
) "mdi:home-assistant"),
) size: 24.0,
); )
} )
}); );
} }
});
}
return result; return result;
} }
@ -319,7 +324,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
menuItems.addAll([ menuItems.addAll([
ListTile( ListTile(
leading: Icon(Icons.settings), leading: Icon(Icons.settings),
title: Text("Connection settings"), title: Text("Settings"),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pushNamed('/connection-settings'); Navigator.of(context).pushNamed('/connection-settings');
@ -452,6 +457,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}); });
}, },
), ),
primary: true,
bottom: empty ? null : TabBar( bottom: empty ? null : TabBar(
tabs: buildUIViewTabs(), tabs: buildUIViewTabs(),
isScrollable: true, isScrollable: true,
@ -472,7 +478,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
), ),
) )
: :
_homeAssistant.buildViews(context) _homeAssistant.buildViews(context, _useLovelaceUI)
); );
} }

View File

@ -20,6 +20,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
String _newSocketProtocol = "wss"; String _newSocketProtocol = "wss";
String _authType = "access_token"; String _authType = "access_token";
String _newAuthType = "access_token"; String _newAuthType = "access_token";
bool _useLovelace = false;
bool _newUseLovelace = false;
bool _edited = false; bool _edited = false;
FocusNode _domainFocusNode; FocusNode _domainFocusNode;
FocusNode _portFocusNode; FocusNode _portFocusNode;
@ -46,6 +48,11 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
_hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? ""; _hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? "";
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss'; _socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
_authType = _newAuthType = prefs.getString("hassio-auth-type") ?? 'access_token'; _authType = _newAuthType = prefs.getString("hassio-auth-type") ?? 'access_token';
try {
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? false;
} catch (e) {
_useLovelace = _newUseLovelace = false;
}
}); });
} }
@ -55,7 +62,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
(_newHassioPort != _hassioPort) || (_newHassioPort != _hassioPort) ||
(_newHassioDomain != _hassioDomain) || (_newHassioDomain != _hassioDomain) ||
(_newSocketProtocol != _socketProtocol) || (_newSocketProtocol != _socketProtocol) ||
(_newAuthType != _authType)); (_newAuthType != _authType) ||
(_newUseLovelace != _useLovelace));
}); });
} }
@ -70,6 +78,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
prefs.setString("hassio-protocol", _newSocketProtocol); prefs.setString("hassio-protocol", _newSocketProtocol);
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http"); prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
prefs.setString("hassio-auth-type", _newAuthType); prefs.setString("hassio-auth-type", _newAuthType);
prefs.setBool("use-lovelace", _newUseLovelace);
} }
@override @override
@ -95,6 +104,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
body: ListView( body: ListView(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
children: <Widget>[ children: <Widget>[
Text(
"Connection settings",
style: TextStyle(
color: Colors.black45,
fontSize: 20.0
),
),
new Row( new Row(
children: [ children: [
Text("Use ssl (HTTPS)"), Text("Use ssl (HTTPS)"),
@ -172,7 +188,29 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
}, },
focusNode: _passwordFocusNode, focusNode: _passwordFocusNode,
onEditingComplete: _checkConfigChanged, onEditingComplete: _checkConfigChanged,
) ),
Padding(
padding: EdgeInsets.only(top: 20.0),
child: Text(
"UI",
style: TextStyle(
color: Colors.black45,
fontSize: 20.0
),
),
),
new Row(
children: [
Text("Use Lovelace UI"),
Switch(
value: _newUseLovelace,
onChanged: (value) {
_newUseLovelace = value;
_checkConfigChanged();
},
)
],
),
], ],
), ),
); );

View File

@ -1,9 +1,9 @@
part of 'main.dart'; part of 'main.dart';
class GroupBasedUI { class HomeAssistantUI {
List<HACView> views; List<HAView> views;
GroupBasedUI() { HomeAssistantUI() {
views = []; views = [];
} }
@ -14,7 +14,6 @@ class GroupBasedUI {
} }
List<Widget> _buildViews(BuildContext context) { List<Widget> _buildViews(BuildContext context) {
TheLogger.log("Debug", "Building UI");
List<Widget> result = []; List<Widget> result = [];
views.forEach((view) { views.forEach((view) {
result.add( result.add(
@ -26,19 +25,59 @@ class GroupBasedUI {
} }
class HACView { class HAView {
List<HACCard> cards = []; List<HACard> cards = [];
List<Entity> badges = []; List<Entity> badges = [];
Entity linkedEntity; Entity linkedEntity;
String name; String name;
String id; String id;
String iconName;
int count; int count;
HACView({ HAView({
this.name, this.name,
this.id, this.id,
this.count this.count,
}); this.iconName,
List<Entity> childEntities
}) {
_fillView(childEntities ?? []);
}
void _fillView(List<Entity> childEntities) {
List<HACard> autoGeneratedCards = [];
childEntities.forEach((entity) {
if (entity.isBadge) {
badges.add(entity);
TheLogger.log("Debug","----Badge: ${entity.entityId}");
} else {
if (!entity.isGroup) {
String groupIdToAdd = "${entity.domain}.${entity.domain}$count";
if (autoGeneratedCards.every((HACard card) => card.id != groupIdToAdd )) {
HACard card = HACard(
id: groupIdToAdd,
name: entity.domain
);
TheLogger.log("Debug","----Creating card: $groupIdToAdd");
card.entities.add(entity);
autoGeneratedCards.add(card);
} else {
autoGeneratedCards.firstWhere((card) => card.id == groupIdToAdd).entities.add(entity);
}
} else {
TheLogger.log("Debug","----Card: ${entity.entityId}");
HACard card = HACard(
name: entity.displayName,
id: entity.entityId,
linkedEntity: entity
);
card.entities.addAll(entity.childEntities);
cards.add(card);
}
}
});
cards.addAll(autoGeneratedCards);
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
return NewViewWidget( return NewViewWidget(
@ -48,7 +87,7 @@ class HACView {
} }
class NewViewWidget extends StatefulWidget { class NewViewWidget extends StatefulWidget {
final HACView view; final HAView view;
const NewViewWidget({ const NewViewWidget({
Key key, Key key,
@ -103,7 +142,7 @@ class NewViewWidgetState extends State<NewViewWidget> {
); );
} }
widget.view.cards.forEach((HACCard card){ widget.view.cards.forEach((HACard card){
result.add( result.add(
card.build(context) card.build(context)
); );
@ -141,13 +180,13 @@ class NewViewWidgetState extends State<NewViewWidget> {
} }
class HACCard { class HACard {
List<Entity> entities = []; List<Entity> entities = [];
Entity linkedEntity; Entity linkedEntity;
String name; String name;
String id; String id;
HACCard({ HACard({
this.name, this.name,
this.id, this.id,
this.linkedEntity this.linkedEntity
@ -163,7 +202,7 @@ class HACCard {
class NewCardWidget extends StatelessWidget { class NewCardWidget extends StatelessWidget {
final HACCard card; final HACard card;
const NewCardWidget({ const NewCardWidget({
Key key, Key key,