diff --git a/lib/entity_class/entity.class.dart b/lib/entity_class/entity.class.dart index 14e6823..d354132 100644 --- a/lib/entity_class/entity.class.dart +++ b/lib/entity_class/entity.class.dart @@ -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(); + } + +} diff --git a/lib/entity_class/stateful_widgets.dart b/lib/entity_class/stateful_widgets.dart index 26cbc33..3f1aa39 100644 --- a/lib/entity_class/stateful_widgets.dart +++ b/lib/entity_class/stateful_widgets.dart @@ -579,9 +579,7 @@ class _ClimateControlWidgetState extends State { Widget _buildTemperatureControls(ClimateEntity entity) { List result = []; - bool empty = true; if (entity.supportTargetTemperature) { - empty = false; result.addAll([ Text( "$_tmpTemperature", @@ -620,7 +618,6 @@ class _ClimateControlWidgetState extends State { ) ]); } else if (entity.supportTargetTemperatureHigh && entity.supportTargetTemperatureLow) { - empty = false; result.addAll([ Text( "$_tmpTargetLow", @@ -697,10 +694,9 @@ class _ClimateControlWidgetState extends State { ) ]); } 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: [ @@ -720,9 +716,7 @@ class _ClimateControlWidgetState extends State { Widget _buildHumidityControls(ClimateEntity entity) { List result = []; - bool empty = true; if (entity.supportTargetHumidity) { - empty = false; result.addAll([ Text( "$_tmpTargetHumidity%", @@ -744,7 +738,7 @@ class _ClimateControlWidgetState extends State { ) ]); } - if (!empty) { + if (result.isNotEmpty) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -824,4 +818,137 @@ class _SelectControlWidgetState extends State { } +} + +class CoverControlWidget extends StatefulWidget { + + CoverControlWidget({Key key}) : super(key: key); + + @override + _CoverControlWidgetState createState() => _CoverControlWidgetState(); +} + +class _CoverControlWidgetState extends State { + + 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: [ + _buildPositionControls(entity), + _buildTiltControls(entity) + ], + ), + ); + } + + Widget _buildPositionControls(CoverEntity entity) { + if (entity.supportSetPosition) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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 controls = []; + if (entity.supportCloseTilt || entity.supportOpenTilt || entity.supportStopTilt) { + controls.add( + CoverEntityTiltControlState() + ); + } + if (entity.supportSetTiltPosition) { + controls.addAll([ + 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); + } + } + } \ No newline at end of file diff --git a/lib/entity_class/stateless_widgets.dart b/lib/entity_class/stateless_widgets.dart index 266fc50..7e1ef47 100644 --- a/lib/entity_class/stateless_widgets.dart +++ b/lib/entity_class/stateless_widgets.dart @@ -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 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 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, + ); + } + } \ No newline at end of file diff --git a/lib/entity_collection.class.dart b/lib/entity_collection.class.dart index 856f712..1d4d959 100644 --- a/lib/entity_collection.class.dart +++ b/lib/entity_collection.class.dart @@ -58,6 +58,9 @@ class EntityCollection { case "climate": { return ClimateEntity(rawEntityData); } + case "cover": { + return CoverEntity(rawEntityData); + } default: { return Entity(rawEntityData); } diff --git a/lib/home_assistant.class.dart b/lib/home_assistant.class.dart index 33a5f97..d70dac3 100644 --- a/lib/home_assistant.class.dart +++ b/lib/home_assistant.class.dart @@ -295,12 +295,16 @@ class HomeAssistant { return sendCompleter.future; } - Future callService(String domain, String service, String entityId, Map additionalParams) { + Future callService(String domain, String service, String entityId, Map 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 += '}}'; diff --git a/lib/main.dart b/lib/main.dart index 6da5f94..99c7377 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 with WidgetsBindingObserver { ); } - void _callService(String domain, String service, String entityId, Map additionalParams) { + void _callService(String domain, String service, String entityId, Map additionalParams) { _homeAssistant.callService(domain, service, entityId, additionalParams).catchError((e) => _setErrorState(e)); } diff --git a/lib/mdi.class.dart b/lib/mdi.class.dart index e11ef77..b03230b 100644 --- a/lib/mdi.class.dart +++ b/lib/mdi.class.dart @@ -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"]; diff --git a/lib/utils.class.dart b/lib/utils.class.dart index 68156b4..8004673 100644 --- a/lib/utils.class.dart +++ b/lib/utils.class.dart @@ -68,7 +68,7 @@ class ServiceCallEvent { String domain; String service; String entityId; - Map additionalParams; + Map additionalParams; ServiceCallEvent(this.domain, this.service, this.entityId, this.additionalParams); } diff --git a/pubspec.yaml b/pubspec.yaml index 23dabbe..61f3861 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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"