mirror of
https://github.com/USA-RedDragon/badnest.git
synced 2025-01-18 22:50:44 +00:00
Inital commit
This commit is contained in:
commit
262622e0a5
90
.gitignore
vendored
Normal file
90
.gitignore
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# IPython Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
39
custom_components/shittynest/__init__.py
Normal file
39
custom_components/shittynest/__init__.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"""The example integration."""
|
||||||
|
import voluptuous as vol
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from .const import DOMAIN
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_EMAIL,
|
||||||
|
CONF_PASSWORD
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
DOMAIN: vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_EMAIL): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Set up the asuswrt component."""
|
||||||
|
if config.get(DOMAIN) is not None:
|
||||||
|
email = config[DOMAIN].get(CONF_EMAIL)
|
||||||
|
password = config[DOMAIN].get(CONF_PASSWORD)
|
||||||
|
else:
|
||||||
|
email = None
|
||||||
|
password = None
|
||||||
|
|
||||||
|
from .api import NestAPI
|
||||||
|
api = NestAPI(
|
||||||
|
email,
|
||||||
|
password
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = api
|
||||||
|
|
||||||
|
return True
|
160
custom_components/shittynest/api.py
Normal file
160
custom_components/shittynest/api.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
from datetime import time, timedelta, datetime
|
||||||
|
import json
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
API_URL = 'https://home.nest.com'
|
||||||
|
|
||||||
|
class NestAPI():
|
||||||
|
def __init__(self, email, password):
|
||||||
|
self._user_id = None
|
||||||
|
self._access_token = None
|
||||||
|
self._device_id = None
|
||||||
|
self._shared_id = None
|
||||||
|
self._czfe_url = None
|
||||||
|
self._compressor_lockout_enabled = None
|
||||||
|
self._compressor_lockout_time = None
|
||||||
|
self._hvac_ac_state = None
|
||||||
|
self._hvac_heater_state = None
|
||||||
|
self.mode = None
|
||||||
|
self._time_to_target = None
|
||||||
|
self._fan_timer_timeout = None
|
||||||
|
self.can_heat = None
|
||||||
|
self.can_cool = None
|
||||||
|
self.has_fan = None
|
||||||
|
self.fan = None
|
||||||
|
self.away = None
|
||||||
|
self.current_temperature = None
|
||||||
|
self.target_temperature = None
|
||||||
|
self.target_temperature_high = None
|
||||||
|
self.target_temperature_low = None
|
||||||
|
self.current_humidity = None
|
||||||
|
|
||||||
|
self._login(email, password)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def _login(self, email = 'jacob.a.mcswain@gmail.com', password = 'ttlshiwwyaJ@'):
|
||||||
|
r = requests.post(f'{API_URL}/session', json={
|
||||||
|
'email': email,
|
||||||
|
'password': password
|
||||||
|
})
|
||||||
|
self._user_id = r.json()['userid']
|
||||||
|
self._access_token = r.json()['access_token']
|
||||||
|
|
||||||
|
def get_action(self):
|
||||||
|
if self._hvac_ac_state:
|
||||||
|
return 'cooling'
|
||||||
|
elif self._hvac_heater_state:
|
||||||
|
return 'heating'
|
||||||
|
else:
|
||||||
|
return 'off'
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
r = requests.post(f'{API_URL}/api/0.1/user/{self._user_id}/app_launch', json={
|
||||||
|
'known_bucket_types': ['shared', 'device'],
|
||||||
|
'known_bucket_versions': []
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Basic {self._access_token}'
|
||||||
|
})
|
||||||
|
|
||||||
|
self._czfe_url = r.json()['service_urls']['urls']['czfe_url']
|
||||||
|
|
||||||
|
for bucket in r.json()['updated_buckets']:
|
||||||
|
if bucket['object_key'].startswith('shared.'):
|
||||||
|
self._shared_id = bucket['object_key']
|
||||||
|
thermostat_data = bucket['value']
|
||||||
|
self.current_temperature = thermostat_data['current_temperature']
|
||||||
|
self.target_temperature = thermostat_data['target_temperature']
|
||||||
|
self._compressor_lockout_enabled = thermostat_data['compressor_lockout_enabled']
|
||||||
|
self._compressor_lockout_time = thermostat_data['compressor_lockout_timeout']
|
||||||
|
self._hvac_ac_state = thermostat_data['hvac_ac_state']
|
||||||
|
self._hvac_heater_state = thermostat_data['hvac_heater_state']
|
||||||
|
self.mode = thermostat_data['target_temperature_type']
|
||||||
|
self.target_temperature_high = thermostat_data['target_temperature_high']
|
||||||
|
self.target_temperature_low = thermostat_data['target_temperature_low']
|
||||||
|
self.can_heat = thermostat_data['can_heat']
|
||||||
|
self.can_cool = thermostat_data['can_cool']
|
||||||
|
elif bucket['object_key'].startswith('device.'):
|
||||||
|
self._device_id = bucket['object_key']
|
||||||
|
thermostat_data = bucket['value']
|
||||||
|
self._time_to_target = thermostat_data['time_to_target']
|
||||||
|
self._fan_timer_timeout = thermostat_data['fan_timer_timeout']
|
||||||
|
self.has_fan = thermostat_data['has_fan']
|
||||||
|
self.fan = thermostat_data['fan_timer_timeout'] > 0
|
||||||
|
self.current_humidity = thermostat_data['current_humidity']
|
||||||
|
self.away = thermostat_data['home_away_input']
|
||||||
|
|
||||||
|
def set_temp(self, temp, temp_high = None):
|
||||||
|
if temp_high is None:
|
||||||
|
requests.post(f'{self._czfe_url}/v5/put', json={
|
||||||
|
'objects': [{
|
||||||
|
'object_key': self._shared_id,
|
||||||
|
'op': 'MERGE',
|
||||||
|
'value':{
|
||||||
|
'target_temperature': temp
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Basic {self._access_token}'
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
requests.post(f'{self._czfe_url}/v5/put', json={
|
||||||
|
'objects': [{
|
||||||
|
'object_key': self._shared_id,
|
||||||
|
'op': 'MERGE',
|
||||||
|
'value': {
|
||||||
|
'target_temperature_low': temp,
|
||||||
|
'target_temperature_high': temp_high
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Basic {self._access_token}'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def set_mode(self, mode):
|
||||||
|
requests.post(f'{self._czfe_url}/v5/put', json={
|
||||||
|
'objects': [{
|
||||||
|
'object_key': self._shared_id,
|
||||||
|
'op': 'MERGE',
|
||||||
|
'value':{
|
||||||
|
'target_temperature_type': mode
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Basic {self._access_token}'
|
||||||
|
})
|
||||||
|
|
||||||
|
def set_fan(self, date):
|
||||||
|
requests.post(f'{self._czfe_url}/v5/put', json={
|
||||||
|
'objects': [{
|
||||||
|
'object_key': self._device_id,
|
||||||
|
'op': 'MERGE',
|
||||||
|
'value':{
|
||||||
|
'fan_timer_timeout': date
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Basic {self._access_token}'
|
||||||
|
})
|
||||||
|
|
||||||
|
def set_eco_mode(self):
|
||||||
|
requests.post(f'{self._czfe_url}/v5/put', json={
|
||||||
|
'objects': [{
|
||||||
|
'object_key': self._device_id,
|
||||||
|
'op': 'MERGE',
|
||||||
|
'value':{
|
||||||
|
'eco': {
|
||||||
|
'mode': 'manual-eco'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Basic {self._access_token}'
|
||||||
|
})
|
249
custom_components/shittynest/climate.py
Normal file
249
custom_components/shittynest/climate.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
"""Demo platform that offers a fake climate device."""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from homeassistant.components.climate import ClimateDevice
|
||||||
|
from homeassistant.components.climate.const import (
|
||||||
|
ATTR_TARGET_TEMP_HIGH,
|
||||||
|
ATTR_TARGET_TEMP_LOW,
|
||||||
|
FAN_AUTO,
|
||||||
|
FAN_ON,
|
||||||
|
HVAC_MODE_AUTO,
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
SUPPORT_PRESET_MODE,
|
||||||
|
SUPPORT_FAN_MODE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||||
|
PRESET_AWAY,
|
||||||
|
PRESET_ECO,
|
||||||
|
PRESET_NONE,
|
||||||
|
CURRENT_HVAC_HEAT,
|
||||||
|
CURRENT_HVAC_IDLE,
|
||||||
|
CURRENT_HVAC_COOL,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
from .api import NestAPI
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
NEST_MODE_HEAT_COOL = "range"
|
||||||
|
NEST_MODE_ECO = "eco"
|
||||||
|
NEST_MODE_HEAT = "heat"
|
||||||
|
NEST_MODE_COOL = "cool"
|
||||||
|
NEST_MODE_OFF = "off"
|
||||||
|
|
||||||
|
MODE_HASS_TO_NEST = {
|
||||||
|
HVAC_MODE_AUTO: NEST_MODE_HEAT_COOL,
|
||||||
|
HVAC_MODE_HEAT: NEST_MODE_HEAT,
|
||||||
|
HVAC_MODE_COOL: NEST_MODE_COOL,
|
||||||
|
HVAC_MODE_OFF: NEST_MODE_OFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_NEST_TO_HASS = {
|
||||||
|
"off": CURRENT_HVAC_IDLE,
|
||||||
|
"heating": CURRENT_HVAC_HEAT,
|
||||||
|
"cooling": CURRENT_HVAC_COOL,
|
||||||
|
}
|
||||||
|
|
||||||
|
MODE_NEST_TO_HASS = {v: k for k, v in MODE_HASS_TO_NEST.items()}
|
||||||
|
|
||||||
|
PRESET_AWAY_AND_ECO = "Away and Eco"
|
||||||
|
|
||||||
|
PRESET_MODES = [PRESET_NONE, PRESET_AWAY, PRESET_ECO, PRESET_AWAY_AND_ECO]
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""Set up the Nest climate device."""
|
||||||
|
add_entities(
|
||||||
|
[
|
||||||
|
ShittyNestClimate(hass.data[DOMAIN]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ShittyNestClimate(ClimateDevice):
|
||||||
|
"""Representation of a demo climate device."""
|
||||||
|
|
||||||
|
def __init__(self, api):
|
||||||
|
"""Initialize the thermostat."""
|
||||||
|
self._name = "Nest"
|
||||||
|
self._unit_of_measurement = TEMP_CELSIUS
|
||||||
|
self._fan_modes = [FAN_ON, FAN_AUTO]
|
||||||
|
|
||||||
|
# Set the default supported features
|
||||||
|
self._support_flags = SUPPORT_TARGET_TEMPERATURE #| SUPPORT_PRESET_MODE
|
||||||
|
|
||||||
|
# Not all nest devices support cooling and heating remove unused
|
||||||
|
self._operation_list = []
|
||||||
|
|
||||||
|
self.device = api
|
||||||
|
|
||||||
|
if self.device.can_heat and self.device.can_cool:
|
||||||
|
self._operation_list.append(HVAC_MODE_AUTO)
|
||||||
|
self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
|
|
||||||
|
# Add supported nest thermostat features
|
||||||
|
if self.device.can_heat:
|
||||||
|
self._operation_list.append(HVAC_MODE_HEAT)
|
||||||
|
|
||||||
|
if self.device.can_cool:
|
||||||
|
self._operation_list.append(HVAC_MODE_COOL)
|
||||||
|
|
||||||
|
self._operation_list.append(HVAC_MODE_OFF)
|
||||||
|
|
||||||
|
# feature of device
|
||||||
|
if self.device.has_fan:
|
||||||
|
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Return the list of supported features."""
|
||||||
|
return self._support_flags
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Return the polling state."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the climate device."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self):
|
||||||
|
"""Return the current temperature."""
|
||||||
|
return self.device.current_temperature
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
"""Return the temperature we try to reach."""
|
||||||
|
if self.device.mode not in (NEST_MODE_HEAT_COOL, NEST_MODE_ECO):
|
||||||
|
return self.device.target_temperature
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_high(self):
|
||||||
|
"""Return the highbound target temperature we try to reach."""
|
||||||
|
if self.device.mode == NEST_MODE_ECO:
|
||||||
|
#TODO: Grab properly
|
||||||
|
return None
|
||||||
|
if self.device.mode == NEST_MODE_HEAT_COOL:
|
||||||
|
return self.device.target_temperature_high
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_low(self):
|
||||||
|
"""Return the lowbound target temperature we try to reach."""
|
||||||
|
if self.device.mode == NEST_MODE_ECO:
|
||||||
|
#TODO: Grab properly
|
||||||
|
return None
|
||||||
|
if self.device.mode == NEST_MODE_HEAT_COOL:
|
||||||
|
return self.device.target_temperature_low
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self):
|
||||||
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
|
return ACTION_NEST_TO_HASS[self.device.get_action()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self):
|
||||||
|
"""Return hvac target hvac state."""
|
||||||
|
if self.device.mode == NEST_MODE_ECO:
|
||||||
|
# We assume the first operation in operation list is the main one
|
||||||
|
return self._operation_list[0]
|
||||||
|
|
||||||
|
return MODE_NEST_TO_HASS[self.device.mode]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self):
|
||||||
|
"""Return the list of available operation modes."""
|
||||||
|
return self._operation_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self):
|
||||||
|
"""Return current preset mode."""
|
||||||
|
if self.device.away and self.device.mode == NEST_MODE_ECO:
|
||||||
|
return PRESET_AWAY_AND_ECO
|
||||||
|
|
||||||
|
if self.device.away:
|
||||||
|
return PRESET_AWAY
|
||||||
|
|
||||||
|
if self.device.mode == NEST_MODE_ECO:
|
||||||
|
return PRESET_ECO
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self):
|
||||||
|
"""Return preset modes."""
|
||||||
|
return PRESET_MODES
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_mode(self):
|
||||||
|
"""Return whether the fan is on."""
|
||||||
|
if self.device.has_fan:
|
||||||
|
# Return whether the fan is on
|
||||||
|
return FAN_ON if self.device.fan else FAN_AUTO
|
||||||
|
# No Fan available so disable slider
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_modes(self):
|
||||||
|
"""Return the list of available fan modes."""
|
||||||
|
if self.device.has_fan:
|
||||||
|
return self._fan_modes
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_temperature(self, **kwargs):
|
||||||
|
"""Set new target temperature."""
|
||||||
|
temp = None
|
||||||
|
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||||
|
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
|
if self.device.mode == NEST_MODE_HEAT_COOL:
|
||||||
|
if target_temp_low is not None and target_temp_high is not None:
|
||||||
|
self.device.set_temp(target_temp_low, target_temp_high)
|
||||||
|
else:
|
||||||
|
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
|
if temp is not None:
|
||||||
|
self.device.set_temp(temp)
|
||||||
|
|
||||||
|
def set_hvac_mode(self, hvac_mode):
|
||||||
|
"""Set operation mode."""
|
||||||
|
self.device.set_mode(MODE_HASS_TO_NEST[hvac_mode])
|
||||||
|
|
||||||
|
def set_fan_mode(self, fan_mode):
|
||||||
|
"""Turn fan on/off."""
|
||||||
|
if self.device.has_fan:
|
||||||
|
if fan_mode == 'on':
|
||||||
|
self.device.set_fan(int(datetime.now().timestamp() + 60 * 30))
|
||||||
|
else:
|
||||||
|
self.device.set_fan(0)
|
||||||
|
|
||||||
|
def set_preset_mode(self, preset_mode):
|
||||||
|
"""Set preset mode."""
|
||||||
|
need_away = preset_mode in (PRESET_AWAY, PRESET_AWAY_AND_ECO)
|
||||||
|
need_eco = preset_mode in (PRESET_ECO, PRESET_AWAY_AND_ECO)
|
||||||
|
is_away = self.device.away
|
||||||
|
is_eco = self.device.mode == NEST_MODE_ECO
|
||||||
|
|
||||||
|
if is_away != need_away:
|
||||||
|
pass
|
||||||
|
#self.device.set_away()
|
||||||
|
|
||||||
|
if is_eco != need_eco:
|
||||||
|
if need_eco:
|
||||||
|
self.device.set_eco_mode()
|
||||||
|
else:
|
||||||
|
self.device.mode = MODE_HASS_TO_NEST[self._operation_list[0]]
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Updates data"""
|
||||||
|
self.device.update()
|
||||||
|
|
1
custom_components/shittynest/const.py
Normal file
1
custom_components/shittynest/const.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
DOMAIN='shittynest'
|
11
custom_components/shittynest/manifest.json
Normal file
11
custom_components/shittynest/manifest.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"domain": "shittynest",
|
||||||
|
"name": "Shitty Nest (A hack around the Nest component to pull from their internal api)",
|
||||||
|
"documentation": "https://custom-components.github.io/shittynest",
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": [
|
||||||
|
"@USA-RedDragon"
|
||||||
|
],
|
||||||
|
"homeassistant": "0.97.0",
|
||||||
|
"requirements": []
|
||||||
|
}
|
4
hacs.json
Normal file
4
hacs.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "Shitty Nest Thermostat",
|
||||||
|
"domains": ["climate"]
|
||||||
|
}
|
23
info.md
Normal file
23
info.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# shittynest
|
||||||
|
|
||||||
|
A shitty Nest thermostats integration that uses the web api to work after Works with Nest was shut down (fuck Google)
|
||||||
|
|
||||||
|
## Drawbacks
|
||||||
|
|
||||||
|
- No proper error handling
|
||||||
|
- Won't work with 2FA enabled accounts
|
||||||
|
- Will only work the for thermostat, I have no other devices to test with
|
||||||
|
- Nest could change their webapp api at any time, making this defunct
|
||||||
|
- Won't work with Google-linked accounts
|
||||||
|
|
||||||
|
## Example configuration.yaml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
shittynest:
|
||||||
|
email: email@domain.com
|
||||||
|
password: !secret nest_password
|
||||||
|
|
||||||
|
climate:
|
||||||
|
- platform: shittynest
|
||||||
|
scan_interval: 10
|
||||||
|
```
|
Loading…
x
Reference in New Issue
Block a user