Resolves #76 Covers support

This commit is contained in:
Yegor Vialov 2018-10-16 17:35:13 +03:00
parent 6e604440c0
commit 2ebba364e3
9 changed files with 367 additions and 17 deletions

View File

@ -75,6 +75,17 @@ class Entity {
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
}
double _getDoubleAttributeValue(String attributeName) {
var temp1 = attributes["$attributeName"];
if (temp1 is int) {
return temp1.toDouble();
} else if (temp1 is double) {
return temp1;
} else {
return null;
}
}
Widget buildDefaultWidget(BuildContext context) {
return EntityModel(
entity: this,
@ -325,6 +336,7 @@ class ClimateEntity extends Entity {
return ClimateControlWidget();
}
@override
double _getDoubleAttributeValue(String attributeName) {
var temp1 = attributes["$attributeName"];
if (temp1 is int) {
@ -391,3 +403,64 @@ class DateTimeEntity extends Entity {
.fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue));
}
}
class CoverEntity extends Entity {
@override
double widgetHeight = 38.0;
static const SUPPORT_OPEN = 1;
static const SUPPORT_CLOSE = 2;
static const SUPPORT_SET_POSITION = 4;
static const SUPPORT_STOP = 8;
static const SUPPORT_OPEN_TILT = 16;
static const SUPPORT_CLOSE_TILT = 32;
static const SUPPORT_STOP_TILT = 64;
static const SUPPORT_SET_TILT_POSITION = 128;
bool get supportOpen => ((attributes["supported_features"] &
CoverEntity.SUPPORT_OPEN) ==
CoverEntity.SUPPORT_OPEN);
bool get supportClose => ((attributes["supported_features"] &
CoverEntity.SUPPORT_CLOSE) ==
CoverEntity.SUPPORT_CLOSE);
bool get supportSetPosition => ((attributes["supported_features"] &
CoverEntity.SUPPORT_SET_POSITION) ==
CoverEntity.SUPPORT_SET_POSITION);
bool get supportStop => ((attributes["supported_features"] &
CoverEntity.SUPPORT_STOP) ==
CoverEntity.SUPPORT_STOP);
bool get supportOpenTilt => ((attributes["supported_features"] &
CoverEntity.SUPPORT_OPEN_TILT) ==
CoverEntity.SUPPORT_OPEN_TILT);
bool get supportCloseTilt => ((attributes["supported_features"] &
CoverEntity.SUPPORT_CLOSE_TILT) ==
CoverEntity.SUPPORT_CLOSE_TILT);
bool get supportStopTilt => ((attributes["supported_features"] &
CoverEntity.SUPPORT_STOP_TILT) ==
CoverEntity.SUPPORT_STOP_TILT);
bool get supportSetTiltPosition => ((attributes["supported_features"] &
CoverEntity.SUPPORT_SET_TILT_POSITION) ==
CoverEntity.SUPPORT_SET_TILT_POSITION);
double get currentPosition => _getDoubleAttributeValue('current_position');
double get currentTiltPosition => _getDoubleAttributeValue('current_tilt_position');
bool get canBeOpened => ((state == "closed") || (state == "closing") || (state == "opening"));
bool get canBeClosed => ((state == "open") || (state == "opening")|| (state == "closing"));
bool get canTiltBeOpened => currentPosition < 100;
bool get canTiltBeClosed => currentPosition > 0;
CoverEntity(Map rawData) : super(rawData);
@override
Widget _buildStatePart(BuildContext context) {
return CoverEntityControlState();
}
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return CoverControlWidget();
}
}

View File

@ -579,9 +579,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
Widget _buildTemperatureControls(ClimateEntity entity) {
List<Widget> result = [];
bool empty = true;
if (entity.supportTargetTemperature) {
empty = false;
result.addAll(<Widget>[
Text(
"$_tmpTemperature",
@ -620,7 +618,6 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
)
]);
} else if (entity.supportTargetTemperatureHigh && entity.supportTargetTemperatureLow) {
empty = false;
result.addAll(<Widget>[
Text(
"$_tmpTargetLow",
@ -697,10 +694,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
)
]);
} else if (entity.supportTargetTemperatureHigh || entity.supportTargetTemperatureLow) {
empty = false;
result.add(Text("Unsupported temperature control. Please, report an issue."));
}
if (!empty) {
if (result.isNotEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@ -720,9 +716,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
Widget _buildHumidityControls(ClimateEntity entity) {
List<Widget> result = [];
bool empty = true;
if (entity.supportTargetHumidity) {
empty = false;
result.addAll(<Widget>[
Text(
"$_tmpTargetHumidity%",
@ -744,7 +738,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
)
]);
}
if (!empty) {
if (result.isNotEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@ -824,4 +818,137 @@ class _SelectControlWidgetState extends State<SelectControlWidget> {
}
}
class CoverControlWidget extends StatefulWidget {
CoverControlWidget({Key key}) : super(key: key);
@override
_CoverControlWidgetState createState() => _CoverControlWidgetState();
}
class _CoverControlWidgetState extends State<CoverControlWidget> {
double _tmpPosition = 0.0;
double _tmpTiltPosition = 0.0;
bool _changedHere = false;
void _setNewPosition(CoverEntity entity, double position) {
setState(() {
_tmpPosition = position.roundToDouble();
_changedHere = true;
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()}));
});
}
void _setNewTiltPosition(CoverEntity entity, double position) {
setState(() {
_tmpTiltPosition = position.roundToDouble();
_changedHere = true;
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_tilt_position", entity.entityId,{"tilt_position": _tmpTiltPosition.round()}));
});
}
void _resetVars(CoverEntity entity) {
_tmpPosition = entity.currentPosition;
_tmpTiltPosition = entity.currentTiltPosition;
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final CoverEntity entity = entityModel.entity;
if (_changedHere) {
_changedHere = false;
} else {
_resetVars(entity);
}
return Padding(
padding: EdgeInsets.fromLTRB(entity.leftWidgetPadding, entity.rowPadding, entity.rightWidgetPadding, 0.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_buildPositionControls(entity),
_buildTiltControls(entity)
],
),
);
}
Widget _buildPositionControls(CoverEntity entity) {
if (entity.supportSetPosition) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(
0.0, entity.rowPadding, 0.0, entity.rowPadding),
child: Text("Position", style: TextStyle(
fontSize: entity.stateFontSize
)),
),
Slider(
value: _tmpPosition,
min: 0.0,
max: 100.0,
divisions: 10,
onChanged: (double value) {
setState(() {
_tmpPosition = value.roundToDouble();
_changedHere = true;
});
},
onChangeEnd: (double value) => _setNewPosition(entity, value),
),
Container(height: entity.rowPadding,)
],
);
} else {
return Container(width: 0.0, height: 0.0);
}
}
Widget _buildTiltControls(CoverEntity entity) {
List<Widget> controls = [];
if (entity.supportCloseTilt || entity.supportOpenTilt || entity.supportStopTilt) {
controls.add(
CoverEntityTiltControlState()
);
}
if (entity.supportSetTiltPosition) {
controls.addAll(<Widget>[
Slider(
value: _tmpTiltPosition,
min: 0.0,
max: 100.0,
divisions: 10,
onChanged: (double value) {
setState(() {
_tmpTiltPosition = value.roundToDouble();
_changedHere = true;
});
},
onChangeEnd: (double value) => _setNewTiltPosition(entity, value),
),
Container(height: entity.rowPadding,)
]);
}
if (controls.isNotEmpty) {
controls.insert(0, Padding(
padding: EdgeInsets.fromLTRB(
0.0, entity.rowPadding, 0.0, entity.rowPadding),
child: Text("Tilt position", style: TextStyle(
fontSize: entity.stateFontSize
)),
));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: controls,
);
} else {
return Container(width: 0.0, height: 0.0);
}
}
}

View File

@ -434,4 +434,136 @@ class DateTimeStateWidget extends StatelessWidget {
);
}
}
class CoverEntityControlState extends StatelessWidget {
void _open(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(entity.domain, "open_cover", entity.entityId, null));
}
void _close(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(entity.domain, "close_cover", entity.entityId, null));
}
void _stop(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(entity.domain, "stop_cover", entity.entityId, null));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final CoverEntity entity = entityModel.entity;
List<Widget> buttons = [];
if (entity.supportOpen) {
buttons.add(
IconButton(
icon: Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-up"),
size: entity.iconSize,
),
onPressed: entity.canBeOpened ? () =>_open(entity) : null
)
);
} else {
buttons.add(Container(width: entity.iconSize+20.0,));
}
if (entity.supportStop) {
buttons.add(
IconButton(
icon: Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:stop"),
size: entity.iconSize,
),
onPressed: () => _stop(entity)
)
);
} else {
buttons.add(Container(width: entity.iconSize+20.0,));
}
if (entity.supportClose) {
buttons.add(
IconButton(
icon: Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-down"),
size: entity.iconSize,
),
onPressed: entity.canBeClosed ? () => _close(entity) : null
)
);
} else {
buttons.add(Container(width: entity.iconSize+20.0,));
}
return Row(
children: buttons,
);
}
}
class CoverEntityTiltControlState extends StatelessWidget {
void _open(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(entity.domain, "open_cover_tilt", entity.entityId, null));
}
void _close(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(entity.domain, "close_cover_tilt", entity.entityId, null));
}
void _stop(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(entity.domain, "stop_cover_tilt", entity.entityId, null));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final CoverEntity entity = entityModel.entity;
List<Widget> buttons = [];
if (entity.supportOpenTilt) {
buttons.add(
IconButton(
icon: Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-top-right"),
size: entity.iconSize,
),
onPressed: entity.canTiltBeOpened ? () =>_open(entity) : null
)
);
} else {
buttons.add(Container(width: entity.iconSize+20.0,));
}
if (entity.supportStopTilt) {
buttons.add(
IconButton(
icon: Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:stop"),
size: entity.iconSize,
),
onPressed: () => _stop(entity)
)
);
} else {
buttons.add(Container(width: entity.iconSize+20.0,));
}
if (entity.supportCloseTilt) {
buttons.add(
IconButton(
icon: Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-bottom-left"),
size: entity.iconSize,
),
onPressed: entity.canTiltBeClosed ? () => _close(entity) : null
)
);
} else {
buttons.add(Container(width: entity.iconSize+20.0,));
}
return Row(
children: buttons,
);
}
}

View File

@ -58,6 +58,9 @@ class EntityCollection {
case "climate": {
return ClimateEntity(rawEntityData);
}
case "cover": {
return CoverEntity(rawEntityData);
}
default: {
return Entity(rawEntityData);
}

View File

@ -295,12 +295,16 @@ class HomeAssistant {
return sendCompleter.future;
}
Future callService(String domain, String service, String entityId, Map<String, String> additionalParams) {
Future callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
_incrementMessageId();
String message = '{"id": $_currentMessageId, "type": "call_service", "domain": "$domain", "service": "$service", "service_data": {"entity_id": "$entityId"';
if (additionalParams != null) {
additionalParams.forEach((name, value){
message += ', "$name" : "$value"';
if ((value is double) || (value is int)) {
message += ', "$name" : $value';
} else {
message += ', "$name" : "$value"';
}
});
}
message += '}}';

View File

@ -30,7 +30,7 @@ part 'card_class.dart';
EventBus eventBus = new EventBus();
const String appName = "HA Client";
const appVersion = "0.2.5.36";
const appVersion = "0.2.5.37";
String homeAssistantWebHost;
@ -224,7 +224,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
);
}
void _callService(String domain, String service, String entityId, Map<String, String> additionalParams) {
void _callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
_homeAssistant.callService(domain, service, entityId, additionalParams).catchError((e) => _setErrorState(e));
}

View File

@ -17,7 +17,11 @@ class MaterialDesignIcons {
"sun": "mdi:white-balance-sunny",
"scene": "mdi:google-pages",
"media_player": "mdi:cast",
"climate": "mdi:thermostat"
"climate": "mdi:thermostat",
"cover.open": "mdi:window-open",
"cover.closed": "mdi:window-closed",
"cover.closing": "mdi:window-open",
"cover.opening": "mdi:window-open",
};
static Map _defaultIconsByDeviceClass = {
@ -69,7 +73,14 @@ class MaterialDesignIcons {
//"sensor.illuminance": "mdi:",
"sensor.temperature": "mdi:thermometer",
//"cover.window": "mdi:",
//"cover.garage": "mdi:",
"cover.garage.closed": "mdi:garage",
"cover.garage.open": "mdi:garage-open",
"cover.garage.opening": "mdi:garage-open",
"cover.garage.closing": "mdi:garage-open",
"cover.window.open": "mdi:window-open",
"cover.window.closed": "mdi:window-closed",
"cover.window.closing": "mdi:window-open",
"cover.window.opening": "mdi:window-open",
};
static Map _iconsDataMap = {
"mdi:access-point": 0xf002,
@ -2916,7 +2927,7 @@ class MaterialDesignIcons {
static int getDefaultIconByEntityId(String entityId, String deviceClass, String state) {
String domain = entityId.split(".")[0];
String iconNameByDomain = _defaultIconsByDomains[domain];
String iconNameByDomain = _defaultIconsByDomains["$domain.$state"] ?? _defaultIconsByDomains["$domain"];
String iconNameByDeviceClass;
if (deviceClass != null) {
iconNameByDeviceClass = _defaultIconsByDeviceClass["$domain.$deviceClass.$state"] ?? _defaultIconsByDeviceClass["$domain.$deviceClass"];

View File

@ -68,7 +68,7 @@ class ServiceCallEvent {
String domain;
String service;
String entityId;
Map<String, String> additionalParams;
Map<String, dynamic> additionalParams;
ServiceCallEvent(this.domain, this.service, this.entityId, this.additionalParams);
}

View File

@ -1,7 +1,7 @@
name: hass_client
description: Home Assistant Android Client
version: 0.2.5+36
version: 0.2.5+37
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"