This repository has been archived on 2023-11-18. You can view files and clone it, but cannot push or open issues or pull requests.
ha_client/lib/home_assistant.class.dart

482 lines
16 KiB
Dart
Raw Normal View History

2018-09-25 22:47:06 +03:00
part of 'main.dart';
class HomeAssistant {
2019-03-26 00:18:30 +02:00
static final HomeAssistant _instance = HomeAssistant._internal();
factory HomeAssistant() {
return _instance;
}
2018-10-25 00:05:29 +03:00
EntityCollection entities;
2018-10-27 00:54:05 +03:00
HomeAssistantUI ui;
2018-09-25 22:47:06 +03:00
Map _instanceConfig = {};
String _userName;
HSVColor savedColor;
String fcmToken;
2018-10-27 00:54:05 +03:00
Map _rawLovelaceData;
2019-03-13 16:39:23 +02:00
List<Panel> panels = [];
2018-10-08 23:11:56 +03:00
Duration fetchTimeout = Duration(seconds: 30);
2018-10-02 18:05:50 +03:00
2018-11-04 21:02:12 +02:00
String get locationName {
2019-04-05 11:48:41 +03:00
if (Connection().useLovelace) {
2018-11-04 21:02:12 +02:00
return ui?.title ?? "";
} else {
return _instanceConfig["location_name"] ?? "";
}
}
String get userName => _userName ?? locationName;
2018-10-08 23:11:56 +03:00
String get userAvatarText => userName.length > 0 ? userName[0] : "";
2019-03-21 14:08:07 +02:00
bool get isNoEntities => entities == null || entities.isEmpty;
bool get isNoViews => ui == null || ui.isEmpty;
bool get isMobileAppEnabled => _instanceConfig["components"] != null && (_instanceConfig["components"] as List).contains("mobile_app");
HomeAssistant._internal() {
2019-04-05 11:48:41 +03:00
Connection().onStateChangeCallback = _handleEntityStateChange;
Device().loadDeviceInfo();
2018-09-25 22:47:06 +03:00
}
2019-03-26 00:18:30 +02:00
Completer _fetchCompleter;
2018-10-08 23:11:56 +03:00
2019-04-05 11:48:41 +03:00
Future fetchData() {
2019-03-26 00:18:30 +02:00
if (_fetchCompleter != null && !_fetchCompleter.isCompleted) {
Logger.w("Previous data fetch is not completed yet");
return _fetchCompleter.future;
2018-10-08 23:11:56 +03:00
}
2019-04-05 13:06:14 +03:00
if (entities == null) entities = EntityCollection(Connection().httpWebHost);
2019-03-26 00:18:30 +02:00
_fetchCompleter = Completer();
2018-10-07 18:27:10 +03:00
List<Future> futures = [];
futures.add(_getStates());
2019-04-05 11:48:41 +03:00
if (Connection().useLovelace) {
2018-10-27 00:54:05 +03:00
futures.add(_getLovelace());
}
2018-10-07 18:27:10 +03:00
futures.add(_getConfig());
futures.add(_getServices());
futures.add(_getUserInfo());
2019-03-13 16:39:23 +02:00
futures.add(_getPanels());
futures.add(Connection().sendSocketMessage(
type: "subscribe_events",
additionalData: {"event_type": "state_changed"},
));
2019-03-26 00:18:30 +02:00
Future.wait(futures).then((_) {
2019-04-19 21:43:52 +03:00
if (isMobileAppEnabled) {
_createUI();
_fetchCompleter.complete();
checkAppRegistration();
2019-04-19 21:43:52 +03:00
} else {
_fetchCompleter.completeError(HAError("Mobile app component not found", actions: [HAErrorAction.tryAgain(), HAErrorAction(type: HAErrorActionType.URL ,title: "Help",url: "http://ha-client.homemade.systems/docs#mobile-app")]));
}
}).catchError((e) {
2019-03-26 00:18:30 +02:00
_fetchCompleter.completeError(e);
});
2019-03-26 00:18:30 +02:00
return _fetchCompleter.future;
2018-09-25 22:47:06 +03:00
}
2019-03-20 23:38:57 +02:00
Future logout() async {
2019-03-21 14:08:07 +02:00
Logger.d("Logging out...");
2019-04-05 13:06:14 +03:00
await Connection().logout().then((_) {
2019-03-26 00:18:30 +02:00
ui?.clear();
entities?.clear();
panels?.clear();
2019-03-26 00:18:30 +02:00
});
}
Map _getAppRegistrationData() {
return {
"app_version": "$appVersion",
"device_name": "$userName's ${Device().model}",
"manufacturer": Device().manufacturer,
"model": Device().model,
"os_name": Device().osName,
"os_version": Device().osVersion,
"app_data": {
"push_token": "$fcmToken",
"push_url": "https://us-central1-ha-client-c73c4.cloudfunctions.net/sendPushNotification"
}
};
}
Future checkAppRegistration({bool forceRegister: false}) {
Completer completer = Completer();
if (Connection().webhookId == null || forceRegister) {
Logger.d("Mobile app was not registered yet or need to be reseted. Registering...");
var registrationData = _getAppRegistrationData();
registrationData.addAll({
"app_id": "ha_client",
"app_name": "$appName",
"supports_encryption": false,
});
Connection().sendHTTPPost(
endPoint: "/api/mobile_app/registrations",
includeAuthHeader: true,
data: json.encode(registrationData)
).then((response) {
Logger.d("Processing registration responce...");
var responseObject = json.decode(response);
SharedPreferences.getInstance().then((prefs) {
prefs.setString("app-webhook-id", responseObject["webhook_id"]);
Connection().webhookId = responseObject["webhook_id"];
completer.complete();
eventBus.fire(ShowDialogEvent(
2019-06-21 12:53:03 +03:00
title: "Mobile app Integration was created",
body: "HA Client was registered as MobileApp in your Home Assistant. To start using notifications you need to restart your Home Assistant",
positiveText: "Restart now",
negativeText: "Later",
onPositive: () {
Connection().callService(domain: "homeassistant", service: "restart", entityId: null);
},
));
});
}).catchError((e) {
completer.complete();
Logger.e("Error registering the app: ${e.toString()}");
});
return completer.future;
} else {
Logger.d("App was previously registered. Checking...");
var updateData = {
"type": "update_registration",
"data": _getAppRegistrationData()
};
Connection().sendHTTPPost(
endPoint: "/api/webhook/${Connection().webhookId}",
includeAuthHeader: false,
data: json.encode(updateData)
).then((response) {
Logger.d("App registration works fine");
completer.complete();
}).catchError((e) {
if (e['code'] != null && e['code'] == 410) {
Logger.e("This integration was removed.");
eventBus.fire(ShowDialogEvent(
title: "App registration was removed or something went wrong",
body: "Looks like app integration was removed from your Home Assistant. HA Client needs to be registered on your Home Assistant server to make it possible to use notifications and other useful stuff.",
positiveText: "Register now",
negativeText: "Cancel",
onPositive: () {
SharedPreferences.getInstance().then((prefs) {
prefs.remove("app-webhook-id");
Connection().webhookId = null;
HomeAssistant().checkAppRegistration();
});
},
));
} else {
Logger.e("Error updating app registration: ${e.toString()}");
}
completer.complete();
});
return completer.future;
}
}
2019-03-02 18:00:25 +02:00
Future _getConfig() async {
2019-04-05 13:06:14 +03:00
await Connection().sendSocketMessage(type: "get_config").then((data) {
_instanceConfig = Map.from(data);
}).catchError((e) {
2019-04-19 21:43:52 +03:00
throw HAError("Error getting config: ${e}");
2019-03-21 16:55:25 +02:00
});
2018-09-25 22:47:06 +03:00
}
2019-03-02 18:00:25 +02:00
Future _getStates() async {
2019-04-05 13:06:14 +03:00
await Connection().sendSocketMessage(type: "get_states").then(
(data) => entities.parse(data)
).catchError((e) {
2019-04-19 21:43:52 +03:00
throw HAError("Error getting states: $e");
});
2019-03-20 19:01:30 +02:00
}
2019-03-02 18:00:25 +02:00
Future _getLovelace() async {
2019-04-05 13:06:14 +03:00
await Connection().sendSocketMessage(type: "lovelace/config").then((data) => _rawLovelaceData = data).catchError((e) {
2019-04-19 21:43:52 +03:00
throw HAError("Error getting lovelace config: $e");
});
2018-10-27 00:54:05 +03:00
}
2019-03-02 18:00:25 +02:00
Future _getUserInfo() async {
_userName = null;
2019-04-05 13:06:14 +03:00
await Connection().sendSocketMessage(type: "auth/current_user").then((data) => _userName = data["name"]).catchError((e) {
Logger.w("Can't get user info: ${e}");
});
}
2019-03-02 18:00:25 +02:00
Future _getServices() async {
2019-04-05 13:06:14 +03:00
await Connection().sendSocketMessage(type: "get_services").then((data) => Logger.d("Services received")).catchError((e) {
Logger.w("Can't get services: ${e}");
});
2018-09-25 22:47:06 +03:00
}
2019-03-13 16:39:23 +02:00
Future _getPanels() async {
panels.clear();
2019-04-05 13:06:14 +03:00
await Connection().sendSocketMessage(type: "get_panels").then((data) {
data.forEach((k,v) {
String title = v['title'] == null ? "${k[0].toUpperCase()}${k.substring(1)}" : "${v['title'][0].toUpperCase()}${v['title'].substring(1)}";
panels.add(Panel(
id: k,
type: v["component_name"],
title: title,
urlPath: v["url_path"],
config: v["config"],
icon: v["icon"]
)
);
});
}).catchError((e) {
2019-04-19 21:43:52 +03:00
throw HAError("Error getting panels list: $e");
});
2018-09-25 22:47:06 +03:00
}
void _handleEntityStateChange(Map eventData) {
2018-10-27 01:24:23 +03:00
//TheLogger.debug( "New state for ${eventData['entity_id']}");
2019-04-19 14:40:05 +03:00
if (_fetchCompleter.isCompleted) {
Map data = Map.from(eventData);
eventBus.fire(new StateChangedEvent(
entityId: data["entity_id"],
needToRebuildUI: entities.updateState(data)
));
}
2018-09-25 22:47:06 +03:00
}
2018-10-27 00:54:05 +03:00
void _parseLovelace() {
Logger.d("--Title: ${_rawLovelaceData["title"]}");
2018-11-04 21:02:12 +02:00
ui.title = _rawLovelaceData["title"];
2018-10-27 00:54:05 +03:00
int viewCounter = 0;
Logger.d("--Views count: ${_rawLovelaceData['views'].length}");
2018-10-27 00:54:05 +03:00
_rawLovelaceData["views"].forEach((rawView){
Logger.d("----view id: ${rawView['id']}");
2018-10-27 00:54:05 +03:00
HAView view = HAView(
count: viewCounter,
2018-11-04 18:26:31 +02:00
id: "${rawView['id']}",
2018-10-27 00:54:05 +03:00
name: rawView['title'],
iconName: rawView['icon']
);
2019-01-25 22:55:41 +02:00
if (rawView['badges'] != null && rawView['badges'] is List) {
rawView['badges'].forEach((entity) {
if (entities.isExist(entity)) {
Entity e = entities.get(entity);
view.badges.add(e);
}
});
}
2018-10-27 00:54:05 +03:00
view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? []));
ui.views.add(
view
);
viewCounter += 1;
});
}
List<HACard> _createLovelaceCards(List rawCards) {
List<HACard> result = [];
rawCards.forEach((rawCard){
try {
bool isThereCardOptionsInside = rawCard["card"] != null;
HACard card = HACard(
id: "card",
name: isThereCardOptionsInside ? rawCard["card"]["title"] ??
rawCard["card"]["name"] : rawCard["title"] ?? rawCard["name"],
type: isThereCardOptionsInside
? rawCard["card"]['type']
: rawCard['type'],
columnsCount: isThereCardOptionsInside
? rawCard["card"]['columns'] ?? 4
: rawCard['columns'] ?? 4,
showName: isThereCardOptionsInside ? rawCard["card"]['show_name'] ??
true : rawCard['show_name'] ?? true,
showState: isThereCardOptionsInside
? rawCard["card"]['show_state'] ?? true
: rawCard['show_state'] ?? true,
showEmpty: rawCard['show_empty'] ?? true,
stateFilter: rawCard['state_filter'] ?? [],
2019-01-29 15:00:15 +02:00
states: rawCard['states'],
content: rawCard['content']
);
if (rawCard["cards"] != null) {
card.childCards = _createLovelaceCards(rawCard["cards"]);
2018-11-25 20:44:19 +02:00
}
rawCard["entities"]?.forEach((rawEntity) {
if (rawEntity is String) {
if (entities.isExist(rawEntity)) {
card.entities.add(EntityWrapper(entity: entities.get(rawEntity)));
2019-03-12 23:35:33 +02:00
} else {
card.entities.add(EntityWrapper(entity: Entity.missed(rawEntity)));
}
} else {
if (rawEntity["type"] == "divider") {
card.entities.add(EntityWrapper(entity: Entity.divider()));
} else if (rawEntity["type"] == "section") {
card.entities.add(EntityWrapper(entity: Entity.section(rawEntity["label"] ?? "")));
} else if (rawEntity["type"] == "call-service") {
Map uiActionData = {
"tap_action": {
"action": EntityUIAction.callService,
"service": rawEntity["service"],
"service_data": rawEntity["service_data"]
},
"hold_action": EntityUIAction.none
};
card.entities.add(EntityWrapper(
entity: Entity.callService(
icon: rawEntity["icon"],
name: rawEntity["name"],
service: rawEntity["service"],
actionName: rawEntity["action_name"]
),
uiAction: EntityUIAction(rawEntityData: uiActionData)
)
);
} else if (rawEntity["type"] == "weblink") {
Map uiActionData = {
"tap_action": {
"action": EntityUIAction.navigate,
"service": rawEntity["url"]
},
"hold_action": EntityUIAction.none
};
card.entities.add(EntityWrapper(
entity: Entity.weblink(
icon: rawEntity["icon"],
name: rawEntity["name"],
url: rawEntity["url"]
),
uiAction: EntityUIAction(rawEntityData: uiActionData)
)
);
} else if (entities.isExist(rawEntity["entity"])) {
Entity e = entities.get(rawEntity["entity"]);
card.entities.add(
EntityWrapper(
entity: e,
2019-01-25 23:30:23 +02:00
displayName: rawEntity["name"],
2019-01-25 22:45:54 +02:00
icon: rawEntity["icon"],
uiAction: EntityUIAction(rawEntityData: rawEntity)
)
);
2019-03-12 23:35:33 +02:00
} else {
card.entities.add(EntityWrapper(entity: Entity.missed(rawEntity["entity"])));
}
2018-11-25 20:44:19 +02:00
}
});
if (rawCard["entity"] != null) {
var en = rawCard["entity"];
if (en is String) {
if (entities.isExist(en)) {
Entity e = entities.get(en);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
2019-01-25 22:45:54 +02:00
icon: rawCard["icon"],
2019-01-25 23:30:23 +02:00
displayName: rawCard["name"],
uiAction: EntityUIAction(rawEntityData: rawCard)
);
2019-03-12 23:35:33 +02:00
} else {
card.linkedEntityWrapper = EntityWrapper(entity: Entity.missed(en));
}
} else {
if (entities.isExist(en["entity"])) {
Entity e = entities.get(en["entity"]);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
2019-01-25 22:45:54 +02:00
icon: en["icon"],
2019-01-25 23:30:23 +02:00
displayName: en["name"],
uiAction: EntityUIAction(rawEntityData: rawCard)
);
2019-03-12 23:35:33 +02:00
} else {
card.linkedEntityWrapper = EntityWrapper(entity: Entity.missed(en["entity"]));
}
2018-11-15 19:08:47 +02:00
}
2018-10-27 17:28:47 +03:00
}
result.add(card);
} catch (e) {
Logger.e("There was an error parsing card: ${e.toString()}");
2018-10-27 00:54:05 +03:00
}
});
return result;
2018-09-25 22:47:06 +03:00
}
2018-10-27 00:54:05 +03:00
void _createUI() {
2018-11-04 21:02:12 +02:00
ui = HomeAssistantUI();
2019-04-05 11:48:41 +03:00
if ((Connection().useLovelace) && (_rawLovelaceData != null)) {
Logger.d("Creating Lovelace UI");
2018-10-27 00:54:05 +03:00
_parseLovelace();
} else {
Logger.d("Creating group-based UI");
2018-10-27 00:54:05 +03:00
int viewCounter = 0;
if (!entities.hasDefaultView) {
HAView view = HAView(
count: viewCounter,
id: "group.default_view",
name: "Home",
childEntities: entities.filterEntitiesForDefaultView()
);
ui.views.add(
view
);
viewCounter += 1;
2018-10-25 00:54:20 +03:00
}
2018-10-27 00:54:05 +03:00
entities.viewEntities.forEach((viewEntity) {
HAView view = HAView(
count: viewCounter,
id: viewEntity.entityId,
name: viewEntity.displayName,
childEntities: viewEntity.childEntities
);
view.linkedEntity = viewEntity;
ui.views.add(
view
);
viewCounter += 1;
});
}
2018-10-25 00:54:20 +03:00
}
2019-03-19 23:07:40 +02:00
Widget buildViews(BuildContext context, TabController tabController) {
return ui.build(context, tabController);
2018-10-07 02:17:14 +03:00
}
2018-10-02 17:23:19 +03:00
}
2019-03-26 00:18:30 +02:00
/*
2018-10-02 17:23:19 +03:00
class SendMessageQueue {
int _messageTimeout;
List<HAMessage> _queue = [];
SendMessageQueue(this._messageTimeout);
void add(String message) {
_queue.add(HAMessage(_messageTimeout, message));
}
List<String> getActualMessages() {
_queue.removeWhere((item) => item.isExpired());
List<String> result = [];
_queue.forEach((haMessage){
result.add(haMessage.message);
});
this.clear();
return result;
}
void clear() {
_queue.clear();
}
}
class HAMessage {
DateTime _timeStamp;
int _messageTimeout;
String message;
HAMessage(this._messageTimeout, this.message) {
_timeStamp = DateTime.now();
}
bool isExpired() {
2018-10-07 12:14:48 +03:00
return DateTime.now().difference(_timeStamp).inSeconds > _messageTimeout;
2018-10-02 17:23:19 +03:00
}
2019-03-26 00:18:30 +02:00
}*/