Skip to content

Commit

Permalink
Big clean up of code and configurable temperature ranges for climate
Browse files Browse the repository at this point in the history
  • Loading branch information
scottyphillips committed Feb 24, 2022
1 parent cf9692e commit 531c86d
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 78 deletions.
105 changes: 41 additions & 64 deletions custom_components/echonetlite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.util import Throttle
from .const import DOMAIN
from .const import DOMAIN, USER_OPTIONS, TEMP_OPTIONS
from aioudp import UDPServer
# from pychonet import Factory

from pychonet import ECHONETAPIClient
from pychonet.EchonetInstance import (
ENL_GETMAP,
Expand Down Expand Up @@ -55,17 +55,16 @@
ENL_STATUS, ENL_BRIGHTNESS, ENL_COLOR_TEMP
]



async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.async_on_unload(entry.add_update_listener(update_listener))
host = None
udp = None
loop = None
server = None

_LOGGER.debug(f"pychonet version {VERSION}")
if DOMAIN in hass.data: # maybe set up by config entry?
_LOGGER.debug(f"{hass.data[DOMAIN]} has already been setup..")
_LOGGER.debug(f"{hass.data[DOMAIN]} is already running.")
server = hass.data[DOMAIN]['api']
hass.data[DOMAIN].update({entry.entry_id: []})
else: # setup API
Expand All @@ -77,7 +76,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
server = ECHONETAPIClient(server=udp, loop=loop)
server._message_timeout = 300
hass.data[DOMAIN].update({"api": server})
_LOGGER.debug(f"pychonet version in use is {VERSION}")


for instance in entry.data["instances"]:
echonetlite = None
Expand All @@ -88,7 +87,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
getmap = instance["getmap"]
setmap = instance["setmap"]
uid = instance["uid"]
_LOGGER.debug(f'{instance["uid"]} is the UID..')

# manually update API states using config entry data
if host not in list(server._state):
Expand Down Expand Up @@ -155,40 +153,19 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return unload_ok


# TODO FIX CODE REPETITION and update for Air Cleaner
# TODO update for Air Cleaner
async def update_listener(hass, entry):
for instance in hass.data[DOMAIN][entry.entry_id]:
if instance['instance']['eojgc'] == 1 and instance['instance']['eojcc'] == 48:
if entry.options.get("fan_settings") is not None: # check if options has been created
if len(entry.options.get("fan_settings")) > 0: # if it has been created then check list length.
instance["echonetlite"]._user_options.update({ENL_FANSPEED: entry.options.get("fan_settings")})
else:
instance["echonetlite"]._user_options.update({ENL_FANSPEED: False})

if entry.options.get("swing_horiz") is not None:
if len(entry.options.get("swing_horiz")) > 0:
instance["echonetlite"]._user_options.update({ENL_AIR_HORZ: entry.options.get("swing_horiz")})
else:
instance["echonetlite"]._user_options.update({ENL_AIR_HORZ: False})

if entry.options.get("swing_vert") is not None:
if len(entry.options.get("swing_vert")) > 0:
instance["echonetlite"]._user_options.update({ENL_AIR_VERT: entry.options.get("swing_vert")})
else:
instance["echonetlite"]._user_options.update({ENL_AIR_VERT: False})

if entry.options.get("auto_direction") is not None:
if len(entry.options.get("auto_direction")) > 0:
instance["echonetlite"]._user_options.update({ENL_AUTO_DIRECTION: entry.options.get("auto_direction")})
else:
instance["echonetlite"]._user_options.update({ENL_AUTO_DIRECTION: False})

if entry.options.get("swing_mode") is not None:
if len(entry.options.get("swing_mode")) > 0:
instance["echonetlite"]._user_options.update({ENL_SWING_MODE: entry.options.get("swing_mode")})
else:
instance["echonetlite"]._user_options.update({ENL_SWING_MODE: False})

for option in USER_OPTIONS.keys():
if entry.options.get(USER_OPTIONS[option]["option"]) is not None: # check if options has been created
if len(entry.options.get(USER_OPTIONS[option]["option"])) > 0: # if it has been created then check list length.
instance["echonetlite"]._user_options.update({option: entry.options.get(USER_OPTIONS[option]["option"])})
else:
instance["echonetlite"]._user_options.update({option: False})
for option in TEMP_OPTIONS.keys():
if entry.options.get(option) is not None:
instance["echonetlite"]._user_options.update({option: entry.options.get(option)})

class ECHONETConnector():
"""EchonetAPIConnector is used to centralise API calls for Echonet devices.
Expand All @@ -212,13 +189,13 @@ def __init__(self, instance, api, entry):
self._update_flags_full_list = []
flags = []
if self._eojgc == 1 and self._eojcc == 48:
_LOGGER.debug(f"Create new HomeAirConditioner instance at: {self._host}")
_LOGGER.debug(f"Starting ECHONETLite HomeAirConditioner instance at {self._host}")
flags = HVAC_API_CONNECTOR_DEFAULT_FLAGS
elif self._eojgc == 2 and self._eojcc == 144:
_LOGGER.debug(f"Create new GeneralLighting instance at: {self._host}")
_LOGGER.debug(f"Starting ECHONETLite GeneralLighting instance at {self._host}")
flags = LIGHT_API_CONNECTOR_DEFAULT_FLAGS
else:
_LOGGER.debug(f"Create new Generic instance for {self._eojgc}-{self._eojcc}-{self._eojci} at {self._host}")
_LOGGER.debug(f"Starting ECHONETLite Generic instance for {self._eojgc}-{self._eojcc}-{self._eojci} at {self._host}")
flags = [ENL_STATUS]
for item in self._getPropertyMap:
if item not in list(EPC_SUPER.keys()):
Expand All @@ -240,33 +217,33 @@ def __init__(self, instance, api, entry):
start_index += MAX_UPDATE_BATCH_SIZE
self._update_flag_batches.append(self._update_flags_full_list[start_index:full_list_length])

# TODO this looks messy.
self._user_options = {
ENL_FANSPEED: False,
ENL_AUTO_DIRECTION: False,
ENL_SWING_MODE: False,
ENL_AIR_VERT: False,
ENL_AIR_HORZ: False
ENL_AIR_HORZ: False,
'min_temp_heat': 15,
'max_temp_heat': 35,
'min_temp_cool': 15,
'max_temp_cool': 35,
'min_temp_auto': 15,
'max_temp_auto': 35,
}
# Stitch together user selectable options for fan + swing modes for HVAC
# TODO - fix code repetition
if entry.options.get("fan_settings") is not None: # check if options has been created
if len(entry.options.get("fan_settings")) > 0: # if it has been created then check list length.
self._user_options[ENL_FANSPEED] = entry.options.get("fan_settings")
if entry.options.get("swing_horiz") is not None:
if len(entry.options.get("swing_horiz")) > 0:
self._user_options[ENL_AIR_HORZ] = entry.options.get("swing_horiz")
if entry.options.get("swing_vert") is not None: # check if options has been created
if len(entry.options.get("swing_vert")) > 0:
self._user_options[ENL_AIR_VERT] = entry.options.get("swing_vert")
if entry.options.get("auto_direction") is not None: # check if options has been created
if len(entry.options.get("auto_direction")) > 0:
self._user_options[ENL_AUTO_DIRECTION] = entry.options.get("auto_direction")
if entry.options.get("swing_mode") is not None: # check if options has been created
if len(entry.options.get("swing_mode")) > 0:
self._user_options[ENL_SWING_MODE] = entry.options.get("swing_mode")
# User selectable options for fan + swing modes for HVAC
for option in USER_OPTIONS.keys():
if entry.options.get(USER_OPTIONS[option]['option']) is not None: # check if options has been created
if len(entry.options.get(USER_OPTIONS[option]['option'])) > 0: # if it has been created then check list length.
self._user_options[option] = entry.options.get(USER_OPTIONS[option]['option'])

# Temperature range options for heat, cool and auto modes
for option in TEMP_OPTIONS.keys():
if entry.options.get(option) is not None:
self._user_options[option] = entry.options.get(option)

self._uid = self._api._state[self._host]["instances"][self._eojgc][self._eojcc][self._eojci][ENL_UID]
_LOGGER.debug(f'{self._uid} is the UID in ECHONET connector..')
_LOGGER.debug(f'ECHONET instance UID is {self._uid}')
if self._uid is None:
self._uid = f"{self._host}-{self._eojgc}-{self._eojcc}-{self._eojci}"

Expand All @@ -281,14 +258,14 @@ async def async_update(self, **kwargs):
update_data.update(batch_data)
elif len(flags) == 1:
update_data[flags[0]] = batch_data
_LOGGER.debug(f"{list(update_data.values())}")
_LOGGER.debug(f"ECHONETlite polling update data - {list(update_data.values())}")
if len(update_data) > 0 and False not in list(update_data.values()):
# polling succeded.
if retry > 1:
_LOGGER.debug(f"polling ECHONET Instance host {self._host} succeeded - Retry {retry} of 3")
_LOGGER.debug(f"Polling ECHONET Instance host {self._host} succeeded. Retry {retry} of 3")
self._update_data.update(update_data)
return self._update_data
else:
_LOGGER.debug(f"polling ECHONET Instance host {self._host} timed out - Retry {retry} of 3")
_LOGGER.debug(f"Number of missed ECHONETLite msssages since reboot is - {len(self._api._message_list)}")
_LOGGER.debug(f"Polling ECHONET Instance host {self._host} timed out. Retry {retry} of 3")
_LOGGER.debug(f"Number of missed ECHONETLite msssages since reboot is {len(self._api._message_list)}")
return self._update_data
25 changes: 25 additions & 0 deletions custom_components/echonetlite/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def __init__(self, name, connector, units: UnitSystem, fan_modes=None, swing_ver
if ENL_AIR_VERT in list(self._connector._setPropertyMap):
self._support_flags = self._support_flags | SUPPORT_SWING_MODE
self._hvac_modes = DEFAULT_HVAC_MODES
self._min_temp = self._connector._user_options['min_temp_auto']
self._max_temp = self._connector._user_options['max_temp_auto']


async def async_update(self):
"""Get the latest state from the HVAC."""
Expand Down Expand Up @@ -257,3 +260,25 @@ async def async_turn_on(self):
async def async_turn_off(self):
"""Turn off."""
await self._connector._instance.off()

@property
def min_temp(self) -> int:
"""Return the minimum temperature supported by the HVAC."""
if self.hvac_mode == HVAC_MODE_HEAT:
self._min_temp = self._connector._user_options['min_temp_heat']
if self.hvac_mode == HVAC_MODE_COOL:
self._min_temp = self._connector._user_options['min_temp_cool']
if self.hvac_mode == HVAC_MODE_HEAT_COOL:
self._min_temp = self._connector._user_options['min_temp_auto']
return self._min_temp

@property
def max_temp(self) -> int:
"""Return the maximum temperature supported by the HVAC."""
if self.hvac_mode == HVAC_MODE_HEAT:
self._max_temp = self._connector._user_options['max_temp_heat']
if self.hvac_mode == HVAC_MODE_COOL:
self._max_temp = self._connector._user_options['max_temp_cool']
if self.hvac_mode == HVAC_MODE_HEAT_COOL:
self._max_temp = self._connector._user_options['max_temp_auto']
return self._max_temp
15 changes: 14 additions & 1 deletion custom_components/echonetlite/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from aioudp import UDPServer
# from pychonet import Factory
from pychonet import ECHONETAPIClient
from .const import DOMAIN, USER_OPTIONS
from .const import DOMAIN, USER_OPTIONS, TEMP_OPTIONS


_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -163,6 +163,19 @@ async def async_step_init(self, user_input=None):
USER_OPTIONS[option]['option_list']
)
})

# Handle setting temperature ranges for various modes of operation
for option in list(TEMP_OPTIONS.keys()):
default_temp = TEMP_OPTIONS[option]['min']
if self._config_entry.options.get(option) is not None:
default_temp = self._config_entry.options.get(option)
data_schema_structure.update({
vol.Required(
option,
default=default_temp
): vol.All(vol.Coerce(int), vol.Range(min=TEMP_OPTIONS[option]['min'], max=TEMP_OPTIONS[option]['max']))
})

elif instance['eojgc'] == 0x01 and instance['eojcc'] == 0x35: # AirCleaner
for option in list(USER_OPTIONS.keys()):
if option in instance['setmap']:
Expand Down
10 changes: 9 additions & 1 deletion custom_components/echonetlite/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
}
},
'default': {
CONF_ICON: None,
CONF_ICON: None,
CONF_TYPE: None,
CONF_STATE_CLASS: None,
},
Expand Down Expand Up @@ -157,3 +157,11 @@
ENL_AUTO_DIRECTION: {'option': 'auto_direction', 'option_list': AUTO_DIRECTION_OPTIONS},
ENL_SWING_MODE: {'option': 'swing_mode', 'option_list': SWING_MODE_OPTIONS},
}

TEMP_OPTIONS = {"min_temp_heat": {"min":15, "max":20},
"max_temp_heat": {"min":25, "max":35},
"min_temp_cool": {"min":15, "max":20},
"max_temp_cool": {"min":25, "max":35},
"min_temp_auto": {"min":15, "max":20},
"max_temp_auto": {"min":25, "max":35},
}
4 changes: 2 additions & 2 deletions custom_components/echonetlite/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
entities = []
for entity in hass.data[DOMAIN][config_entry.entry_id]:
if entity['instance']['eojgc'] == 0x02 and entity['instance']['eojcc'] == 0x90: # General Lighting
_LOGGER.debug("Found ECHONET Light")
_LOGGER.debug("Configuring ECHONETlite Light entity")
entities.append(EchonetLight(config_entry.title, entity['echonetlite']))
_LOGGER.debug(f"Number of light devices to be added: {len(entities)}")
async_add_devices(entities, True)
Expand Down Expand Up @@ -129,7 +129,7 @@ async def async_turn_on(self, **kwargs):
if ATTR_COLOR_TEMP in kwargs and COLOR_MODE_COLOR_TEMP in self._supported_color_modes:
# bring the selected color to something we can calculate on
color_scale = (float(kwargs[ATTR_COLOR_TEMP]) - float(self._min_mireds)) / float(self._max_mireds - self._min_mireds)
_LOGGER.debug(f"set color to : {color_scale}")
_LOGGER.debug(f"Set color to : {color_scale}")
# bring the color to
color_scale_echonet = color_scale * (len(self._echonet_mireds) - 1)
# round it to an index
Expand Down
2 changes: 1 addition & 1 deletion custom_components/echonetlite/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"codeowners": [
"@scottyphillips"
],
"version": "3.3.1",
"version": "3.4.0",
"iot_class": "local_polling"
}
11 changes: 5 additions & 6 deletions custom_components/echonetlite/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
async def async_setup_entry(hass, config, async_add_entities, discovery_info=None):
entities = []
for entity in hass.data[DOMAIN][config.entry_id]:
_LOGGER.debug(f"setting up sensor {entity}")
_LOGGER.debug(f"update flags for this sensor are {entity['echonetlite']._update_flags_full_list}")
_LOGGER.debug(f"Configuring ECHONETLite sensor {entity}")
_LOGGER.debug(f"Update flags for this sensor are {entity['echonetlite']._update_flags_full_list}")
eojgc = entity['instance']['eojgc']
eojcc = entity['instance']['eojcc']

# Home Air Conditioner we dont bother exposing all sensors
if eojgc == 1 and eojcc == 48:
_LOGGER.debug("This is an ECHONET climate device so only a few sensors will be created")
_LOGGER.debug("This is an ECHONET climate device so not all sensors will be configured.")
for op_code in ENL_SENSOR_OP_CODES[eojgc][eojcc].keys():
if op_code in entity['instance']['getmap']:
entities.append(
Expand All @@ -43,7 +43,7 @@ async def async_setup_entry(hass, config, async_add_entities, discovery_info=Non
)
)
elif eojgc == 1 and eojcc == 53:
_LOGGER.debug("This is an ECHONET fan device so only a few sensors will be created")
_LOGGER.debug("This is an ECHONET fan device so not all sensors will be configured.")
for op_code in ENL_SENSOR_OP_CODES[eojgc][eojcc].keys():
if op_code in entity['instance']['getmap']:
entities.append(
Expand All @@ -55,7 +55,6 @@ async def async_setup_entry(hass, config, async_add_entities, discovery_info=Non
)
)
else: # handle other ECHONET instances
_LOGGER.debug("Configuring ECHONETlite sensor..")
for op_code in EPC_CODE[eojgc][eojcc]:
if eojgc in ENL_SENSOR_OP_CODES.keys():
if eojcc in ENL_SENSOR_OP_CODES[eojgc].keys():
Expand Down Expand Up @@ -171,7 +170,7 @@ def native_value(self) -> StateType:
return self._instance._update_data[self._op_code]
else:
return STATE_UNAVAILABLE
return None
return STATE_UNAVAILABLE

@property
def native_unit_of_measurement(self):
Expand Down
8 changes: 7 additions & 1 deletion custom_components/echonetlite/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@
"swing_horiz": "Configure Horizontal Swing Settings",
"swing_vert": "Configure Vertical Swing Settings",
"auto_direction": "Configure Auto Direction",
"swing_mode": "Configure Swing Mode"
"swing_mode": "Configure Swing Mode",
"min_temp_heat": "Configure Minimum Temperature for Heating Operation",
"max_temp_heat": "Configure Maximum Temperature for Heating Operation",
"min_temp_cool": "Configure Minimum Temperature for Cooling Operation",
"max_temp_cool": "Configure Maximum Temperature for Cooling Operation",
"min_temp_auto": "Configure Minimum Temperature for Automatic Operation",
"max_temp_auto": "Configure Maximum Temperature for Automatic Operation"
},
"description": "Configure Fan Settings"
}
Expand Down
10 changes: 8 additions & 2 deletions custom_components/echonetlite/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@
"swing_horiz": "Configure Horizontal Swing Settings",
"swing_vert": "Configure Vertical Swing Settings",
"auto_direction": "Configure Auto Direction",
"swing_mode": "Configure Swing Mode"
"swing_mode": "Configure Swing Mode",
"min_temp_heat": "Configure Minimum Temperature for Heating Operation",
"max_temp_heat": "Configure Maximum Temperature for Heating Operation",
"min_temp_cool": "Configure Minimum Temperature for Cooling Operation",
"max_temp_cool": "Configure Maximum Temperature for Cooling Operation",
"min_temp_auto": "Configure Minimum Temperature for Automatic Operation",
"max_temp_auto": "Configure Maximum Temperature for Automatic Operation"
},
"description": "Configure optional fan and swing mode settings"
}
}
},
"title": "ECHONETLite"
}
}

0 comments on commit 531c86d

Please sign in to comment.