Resolves #76 Covers support
This commit is contained in:
parent
6e604440c0
commit
2ebba364e3
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -58,6 +58,9 @@ class EntityCollection {
|
||||
case "climate": {
|
||||
return ClimateEntity(rawEntityData);
|
||||
}
|
||||
case "cover": {
|
||||
return CoverEntity(rawEntityData);
|
||||
}
|
||||
default: {
|
||||
return Entity(rawEntityData);
|
||||
}
|
||||
|
@ -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 += '}}';
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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"];
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user