Skip to content

Commit

Permalink
v0.6.0 (#36)
Browse files Browse the repository at this point in the history
* Add support for climate control presets

* Add button platform for remote commands

* Add select for remote climate and unlock service

* For remote cmds, reauths if session is > 4hrs old
  • Loading branch information
G-Two authored Jan 9, 2022
1 parent 52e3a7b commit c9c94ee
Show file tree
Hide file tree
Showing 18 changed files with 894 additions and 320 deletions.
551 changes: 271 additions & 280 deletions README.md

Large diffs are not rendered by default.

57 changes: 38 additions & 19 deletions custom_components/subaru/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,29 @@
ENTRY_COORDINATOR,
ENTRY_VEHICLES,
FETCH_INTERVAL,
REMOTE_SERVICE_FETCH,
REMOTE_CLIMATE_PRESET_NAME,
REMOTE_SERVICE_REMOTE_START,
SUPPORTED_PLATFORMS,
UPDATE_INTERVAL,
VEHICLE_API_GEN,
VEHICLE_HAS_EV,
VEHICLE_HAS_REMOTE_SERVICE,
VEHICLE_HAS_REMOTE_START,
VEHICLE_HAS_SAFETY_SERVICE,
VEHICLE_LAST_FETCH,
VEHICLE_LAST_UPDATE,
VEHICLE_NAME,
VEHICLE_VIN,
)
from .remote_service import (
SERVICES_THAT_NEED_FETCH,
async_call_remote_service,
get_supported_services,
refresh_subaru,
update_subaru,
)

_LOGGER = logging.getLogger(__name__)

REMOTE_SERVICE_SCHEMA = vol.Schema({vol.Required(VEHICLE_VIN): cv.string})


async def async_setup(hass, base_config):
"""Do nothing since this integration does not support configuration.yml setup."""
Expand All @@ -57,7 +57,7 @@ async def async_setup(hass, base_config):
async def async_setup_entry(hass, entry):
"""Set up Subaru from a config entry."""
config = entry.data
websession = aiohttp_client.async_get_clientsession(hass)
websession = aiohttp_client.async_create_clientsession(hass)

# Backwards compatibility for configs made before v0.3.0
country = config.get(CONF_COUNTRY)
Expand Down Expand Up @@ -119,18 +119,20 @@ async def async_update_data():
async def async_call_service(call):
"""Execute subaru service."""
vin = call.data[VEHICLE_VIN].upper()
arg = None
if call.service == REMOTE_SERVICE_REMOTE_START:
arg = call.data[REMOTE_CLIMATE_PRESET_NAME]

if vin in vehicles:
if call.service != REMOTE_SERVICE_FETCH:
await async_call_remote_service(
hass,
controller,
call.service,
vehicles[vin],
entry.options.get(CONF_NOTIFICATION_OPTION),
)
if call.service in SERVICES_THAT_NEED_FETCH:
await coordinator.async_refresh()
await async_call_remote_service(
hass,
controller,
call.service,
vehicles[vin],
arg,
entry.options.get(CONF_NOTIFICATION_OPTION),
)
await coordinator.async_refresh()
return

hass.components.persistent_notification.create(
Expand All @@ -141,9 +143,25 @@ async def async_call_service(call):
supported_services = get_supported_services(vehicles)

for service in supported_services:
hass.services.async_register(
DOMAIN, service, async_call_service, schema=REMOTE_SERVICE_SCHEMA
)
if service == REMOTE_SERVICE_REMOTE_START:
hass.services.async_register(
DOMAIN,
service,
async_call_service,
schema=vol.Schema(
{
vol.Required(VEHICLE_VIN): cv.string,
vol.Required(REMOTE_CLIMATE_PRESET_NAME): cv.string,
}
),
)
else:
hass.services.async_register(
DOMAIN,
service,
async_call_service,
schema=vol.Schema({vol.Required(VEHICLE_VIN): cv.string}),
)

return True

Expand Down Expand Up @@ -185,7 +203,7 @@ async def refresh_subaru_data(config_entry, vehicle_info, controller):
await update_subaru(vehicle, controller)

# Fetch data from Subaru servers
await controller.fetch(vin, force=True)
await refresh_subaru(vehicle, controller)

# Update our local data that will go to entity states
received_data = await controller.get_data(vin)
Expand All @@ -206,5 +224,6 @@ def get_vehicle_info(controller, vin):
VEHICLE_HAS_REMOTE_SERVICE: controller.get_remote_status(vin),
VEHICLE_HAS_SAFETY_SERVICE: controller.get_safety_status(vin),
VEHICLE_LAST_UPDATE: 0,
VEHICLE_LAST_FETCH: 0,
}
return info
6 changes: 6 additions & 0 deletions custom_components/subaru/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@
SENSOR_CLASS: DEVICE_CLASS_WINDOW,
SENSOR_ON_VALUE: sc.WINDOW_OPEN,
},
{
SENSOR_TYPE: "Sunroof",
SENSOR_FIELD: sc.WINDOW_SUNROOF_STATUS,
SENSOR_CLASS: DEVICE_CLASS_WINDOW,
SENSOR_ON_VALUE: sc.WINDOW_OPEN,
},
]

# Binary Sensor data available to "Subaru Safety Plus" subscribers with PHEV vehicles
Expand Down
123 changes: 123 additions & 0 deletions custom_components/subaru/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Support for Subaru buttons."""
import logging

from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonEntity

from . import DOMAIN as SUBARU_DOMAIN
from .const import (
CONF_NOTIFICATION_OPTION,
ENTRY_CONTROLLER,
ENTRY_COORDINATOR,
ENTRY_VEHICLES,
ICONS,
REMOTE_SERVICE_CHARGE_START,
REMOTE_SERVICE_FETCH,
REMOTE_SERVICE_HORN,
REMOTE_SERVICE_HORN_STOP,
REMOTE_SERVICE_LIGHTS,
REMOTE_SERVICE_LIGHTS_STOP,
REMOTE_SERVICE_REMOTE_START,
REMOTE_SERVICE_REMOTE_STOP,
REMOTE_SERVICE_UPDATE,
VEHICLE_CLIMATE_SELECTED_PRESET,
VEHICLE_HAS_EV,
VEHICLE_HAS_REMOTE_SERVICE,
VEHICLE_HAS_REMOTE_START,
)
from .entity import SubaruEntity
from .remote_service import async_call_remote_service

_LOGGER = logging.getLogger(__name__)

BUTTON_TYPE = "type"
BUTTON_SERVICE = "service"

G1_REMOTE_BUTTONS = [
{BUTTON_TYPE: "Horn Start", BUTTON_SERVICE: REMOTE_SERVICE_HORN},
{BUTTON_TYPE: "Horn Stop", BUTTON_SERVICE: REMOTE_SERVICE_HORN_STOP},
{BUTTON_TYPE: "Lights Start", BUTTON_SERVICE: REMOTE_SERVICE_LIGHTS},
{BUTTON_TYPE: "Lights Stop", BUTTON_SERVICE: REMOTE_SERVICE_LIGHTS_STOP},
{BUTTON_TYPE: "Locate", BUTTON_SERVICE: REMOTE_SERVICE_UPDATE},
{BUTTON_TYPE: "Refresh", BUTTON_SERVICE: REMOTE_SERVICE_FETCH},
]

RES_REMOTE_BUTTONS = [
{BUTTON_TYPE: "Remote Start", BUTTON_SERVICE: REMOTE_SERVICE_REMOTE_START},
{BUTTON_TYPE: "Remote Stop", BUTTON_SERVICE: REMOTE_SERVICE_REMOTE_STOP},
]

EV_REMOTE_BUTTONS = [
{BUTTON_TYPE: "Charge EV", BUTTON_SERVICE: REMOTE_SERVICE_CHARGE_START}
]


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Subaru button by config_entry."""
coordinator = hass.data[SUBARU_DOMAIN][config_entry.entry_id][ENTRY_COORDINATOR]
vehicle_info = hass.data[SUBARU_DOMAIN][config_entry.entry_id][ENTRY_VEHICLES]
entities = []
for vin in vehicle_info:
entities.extend(
create_vehicle_buttons(vehicle_info[vin], coordinator, config_entry)
)
async_add_entities(entities, True)


def create_vehicle_buttons(vehicle_info, coordinator, config_entry):
"""Instantiate all available buttons for the vehicle."""
buttons_to_add = []
if vehicle_info[VEHICLE_HAS_REMOTE_SERVICE]:
buttons_to_add.extend(G1_REMOTE_BUTTONS)

if vehicle_info[VEHICLE_HAS_REMOTE_START] or vehicle_info[VEHICLE_HAS_EV]:
buttons_to_add.extend(RES_REMOTE_BUTTONS)

if vehicle_info[VEHICLE_HAS_EV]:
buttons_to_add.extend(EV_REMOTE_BUTTONS)

return [
SubaruButton(
vehicle_info, coordinator, config_entry, b[BUTTON_TYPE], b[BUTTON_SERVICE],
)
for b in buttons_to_add
]


class SubaruButton(SubaruEntity, ButtonEntity):
"""Representation of a Subaru button."""

def __init__(self, vehicle_info, coordinator, config_entry, entity_type, service):
"""Initialize the button for the vehicle."""
super().__init__(vehicle_info, coordinator)
self.entity_type = entity_type
self.hass_type = BUTTON_DOMAIN
self.config_entry = config_entry
self.service = service
self.arg = None

@property
def icon(self):
"""Return the icon of the sensor."""
if not self.device_class:
return ICONS.get(self.entity_type)

async def async_press(self):
"""Press the button."""
_LOGGER.debug("%s button pressed for %s", self.entity_type, self.car_name)
arg = None
if self.service == REMOTE_SERVICE_REMOTE_START:
arg = self.coordinator.data.get(self.vin).get(
VEHICLE_CLIMATE_SELECTED_PRESET
)
controller = self.hass.data[SUBARU_DOMAIN][self.config_entry.entry_id][
ENTRY_CONTROLLER
]
await async_call_remote_service(
self.hass,
controller,
self.service,
self.vehicle_info,
arg,
self.config_entry.options.get(CONF_NOTIFICATION_OPTION),
)
await self.coordinator.async_refresh()
30 changes: 30 additions & 0 deletions custom_components/subaru/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Constants for the Subaru integration."""
from enum import Enum

import subarulink.const as sc

from homeassistant.const import Platform

DOMAIN = "subaru"
Expand Down Expand Up @@ -49,12 +51,17 @@ def get_by_value(cls, value):
VEHICLE_HAS_REMOTE_SERVICE = "has_remote"
VEHICLE_HAS_SAFETY_SERVICE = "has_safety"
VEHICLE_LAST_UPDATE = "last_update"
VEHICLE_LAST_FETCH = "last_fetch"
VEHICLE_STATUS = "status"
VEHICLE_CLIMATE = "climate"
VEHICLE_CLIMATE_SELECTED_PRESET = "preset_name"

API_GEN_1 = "g1"
API_GEN_2 = "g2"
MANUFACTURER = "Subaru Corp."

ATTR_DOOR = "door"

REMOTE_SERVICE_FETCH = "fetch"
REMOTE_SERVICE_UPDATE = "update"
REMOTE_SERVICE_LOCK = "lock"
Expand All @@ -66,17 +73,40 @@ def get_by_value(cls, value):
REMOTE_SERVICE_REMOTE_START = "remote_start"
REMOTE_SERVICE_REMOTE_STOP = "remote_stop"
REMOTE_SERVICE_CHARGE_START = "charge_start"
REMOTE_CLIMATE_PRESET_NAME = "preset_name"

SERVICE_UNLOCK_SPECIFIC_DOOR = "unlock_specific_door"
UNLOCK_DOOR_ALL = "all"
UNLOCK_DOOR_DRIVERS = "driver"
UNLOCK_DOOR_TAILGATE = "tailgate"
UNLOCK_VALID_DOORS = {
UNLOCK_DOOR_ALL: sc.ALL_DOORS,
UNLOCK_DOOR_DRIVERS: sc.DRIVERS_DOOR,
UNLOCK_DOOR_TAILGATE: sc.TAILGATE_DOOR,
}

SUPPORTED_PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.DEVICE_TRACKER,
Platform.LOCK,
Platform.SENSOR,
Platform.BUTTON,
Platform.SELECT,
]

ICONS = {
"Avg Fuel Consumption": "mdi:leaf",
"EV Range": "mdi:ev-station",
"Odometer": "mdi:road-variant",
"Range": "mdi:gas-station",
"Horn Start": "mdi:volume-high",
"Horn Stop": "mdi:volume-off",
"Lights Start": "mdi:lightbulb-on",
"Lights Stop": "mdi:lightbulb-off",
"Locate": "mdi:car-connected",
"Refresh": "mdi:refresh",
"Remote Start": "mdi:power",
"Remote Stop": "mdi:stop-circle-outline",
"Charge EV": "mdi:ev-station",
"Climate Preset": "mdi:thermometer-lines",
}
Loading

0 comments on commit c9c94ee

Please sign in to comment.