Skip to content

entities platforms (sensor, binary_sensor, switch) #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions custom_components/pyscript/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from .requirements import install_requirements
from .state import State, StateVal
from .trigger import TrigTime
from .entity_manager import EntityManager

_LOGGER = logging.getLogger(LOGGER_PATH)

Expand All @@ -64,6 +65,7 @@

CONFIG_SCHEMA = vol.Schema({DOMAIN: PYSCRIPT_SCHEMA}, extra=vol.ALLOW_EXTRA)

PLATFORMS = ["sensor", "binary_sensor", "switch"]

async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Component setup, run import config flow for each entry in config."""
Expand Down Expand Up @@ -240,6 +242,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
State.init(hass)
State.register_functions()
GlobalContextMgr.init()
EntityManager.init(hass)

for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, component)
)

pyscript_folder = hass.config.path(FOLDER)
if not await hass.async_add_executor_job(os.path.isdir, pyscript_folder):
Expand Down
66 changes: 66 additions & 0 deletions custom_components/pyscript/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Pyscript Binary Sensor Entity"""
from .entity_manager import EntityManager, PyscriptEntity
from homeassistant.components.binary_sensor import BinarySensorEntity

PLATFORM = "binary_sensor"


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Initialize Pyscript Binary Sensor Platform"""
EntityManager.register_platform(PLATFORM, async_add_entities, PyscriptBinarySensor)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Initialize Pyscript Binary Sensor Config"""
return await async_setup_platform(hass, config_entry.data, async_add_entities, discovery_info=None)


class PyscriptBinarySensor(PyscriptEntity, BinarySensorEntity):
"""A Pyscript Binary Sensor Entity"""
platform = PLATFORM

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._device_class = None


# USED BY HOME ASSISTANT
##############################

@property
def is_on(self):
"""Return true if binary sensor is on"""
if self._state == 'on':
return True
return False

@property
def device_class(self):
"""Return the device class of the sensor."""
return self._device_class

# OVERRIDDEN FROM PyscriptEntity
################################

def set_state(self, state):
"""Handle State Validation"""
if state is True:
state = "on"

if state is False:
state = "off"

state = state.lower()

if state not in ("on", "off"):
raise ValueError('BinarySensor state must be "on" or "off"')

super().set_state(state)

# USED IN PYSCRIPT
######################################

async def set_device_class(self, device_class):
"""Set Device Class of Entity"""
self._device_class = device_class
await self.async_update()
184 changes: 184 additions & 0 deletions custom_components/pyscript/entity_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
"""Pyscript Entity Manager"""
from homeassistant.helpers.entity import Entity
from .const import LOGGER_PATH
import logging

_LOGGER = logging.getLogger(LOGGER_PATH + ".entity_manager")


class EntityManager:
"""Entity Manager."""
hass = None

platform_adders = {}
platform_classes = {}
registered_entities = {}

@classmethod
def init(cls, hass):
"""Initialize Class Variables"""
cls.hass = hass

@classmethod
def register_platform(cls, platform, adder, entity_class):
"""Register platform from Home Assistant"""
_LOGGER.debug(
"Platform %s Registered",
platform,
)
cls.platform_adders[platform] = adder
cls.platform_classes[platform] = entity_class
cls.registered_entities[platform] = {}

@classmethod
async def get(cls, ast_ctx, platform, unique_id):
"""Get an Entity from pyscript"""
await cls.wait_platform_registered(platform)
if platform not in cls.registered_entities or unique_id not in cls.registered_entities[platform]:
await cls.create(ast_ctx, platform, unique_id)

return cls.registered_entities[platform][unique_id]

@classmethod
async def create(cls, ast_ctx, platform, unique_id):
"""Create entity from pyscript."""
await cls.wait_platform_registered(platform)
new_entity = cls.platform_classes[platform](cls.hass, ast_ctx, unique_id)
cls.platform_adders[platform]([new_entity])
cls.registered_entities[platform][unique_id] = new_entity

@classmethod
async def wait_platform_registered(cls, platform):
"""Wait for platform registration."""
if platform not in cls.platform_classes:
raise KeyError(f"Platform {platform} not registered.")

return True


class PyscriptEntity(Entity):
"""Base Class for all Pyscript Entities"""
def __init__(self, hass, ast_ctx, unique_id):
self._added = False
self.hass = hass
self.ast_ctx = ast_ctx

self._unique_id = f"{self.platform}_{unique_id}"

self._state = None
self._attributes = {}

self._icon = None
self._name = unique_id

_LOGGER.debug(
"Entity Initialized %s",
self._unique_id,
)

# USED BY HOME ASSISTANT
####################################

@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return self._icon

@property
def name(self):
"""Return the name to use in the frontend, if any."""
return self._name

@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id

@property
def state_attributes(self):
"""Return the state attributes."""
return self._attributes

@property
def device_state_attributes(self):
"""Return device specific state attributes."""
return {
"_unique_id": self._unique_id,
"_global_ctx": self.ast_ctx.name,
}

async def async_added_to_hass(self):
"""Called when Home Assistant adds the entity to the registry"""
self._added = True
await self.async_update()
_LOGGER.debug(
"Entity %s Added to Hass as %s",
self._unique_id,
self.entity_id,
)

@property
def should_poll(self):
"""Return True if entity has to be polled for state.
False if entity pushes its state to HA.
"""
return False

# USED INTERNALLY
#####################################

async def async_update(self):
"""Request an entity update from Home Assistant"""
if self._added:
self.async_write_ha_state()


# OPTIONALLY OVERRIDDEN IN EXTENDED CLASSES
#####################################

def set_state(self, state):
"""Set the State"""
self._state = state

def set_attribute(self, attribute, value):
"""Set a single attribute"""
self._attributes[attribute] = value

def set_all_attributes(self, attributes):
"""Set all Attributes and clear existing values"""
self._attributes = attributes


# TO BE USED IN PYSCRIPT
######################################

async def set(self, state=None, new_attributes=None, **kwargs):
"""Set state and/or attributes from pyscript"""
if state is not None:
self.set_state(state)

if new_attributes is not None:
self.set_all_attributes(new_attributes)

for attribute_name in kwargs:
self.set_attribute(attribute_name, kwargs[attribute_name])

_LOGGER.debug(
"%s (%s) state is now %s (%s)",
self._unique_id,
self.entity_id,
self._state,
self._attributes,
)

await self.async_update()

async def set_name(self, name):
"""set name of entity"""
self._name = name
await self.async_update()

async def set_icon(self, icon):
"""set icon of entity"""
self._icon = icon
await self.async_update()
12 changes: 12 additions & 0 deletions custom_components/pyscript/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from homeassistant.core import Context

from .const import LOGGER_PATH
from .entity_manager import EntityManager

_LOGGER = logging.getLogger(LOGGER_PATH + ".function")

Expand Down Expand Up @@ -101,6 +102,7 @@ def init(cls, hass):
"print": lambda ast_ctx: ast_ctx.get_logger().debug,
"task.name2id": cls.task_name2id_factory,
"task.unique": cls.task_unique_factory,
"em": cls.entity_manager_get_factory,
}
)

Expand Down Expand Up @@ -201,6 +203,16 @@ async def async_sleep(cls, duration):
"""Implement task.sleep()."""
await asyncio.sleep(float(duration))

@classmethod
def entity_manager_get_factory(cls, ctx):
"""Define and return em for this context."""

async def entity_manager_get(platform, name):
"""Implement em"""
return await EntityManager.get(ctx, platform, name)

return entity_manager_get

@classmethod
async def event_fire(cls, event_type, **kwargs):
"""Implement event.fire()."""
Expand Down
59 changes: 59 additions & 0 deletions custom_components/pyscript/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Pyscript Sensor Entity"""
from .entity_manager import EntityManager, PyscriptEntity
from homeassistant.helpers.entity import Entity

PLATFORM = "sensor"


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Initialize Pyscript Sensor Platform"""
EntityManager.register_platform(PLATFORM, async_add_entities, PyscriptSensor)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Initialize Pyscript Sensor Config"""
return await async_setup_platform(hass, config_entry.data, async_add_entities, discovery_info=None)


# inheriting from Entity here because HASS doesn't have SensorEntity
class PyscriptSensor(PyscriptEntity, Entity):
"""A Pyscript Sensor Entity"""
platform = PLATFORM

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._device_class = None
self._unit_of_measurement = None


# USED BY HOME ASSISTANT
##############################

@property
def state(self):
"""Return the state of the sensor."""
return self._state

@property
def device_class(self):
"""Return the device class of the sensor."""
return self._device_class

@property
def unit_of_measurement(self):
"""Return the unit of measurement for the sensor."""
return self._unit_of_measurement


# USED IN PYSCRIPT
######################################

async def set_device_class(self, device_class):
"""Set device class of entity"""
self._device_class = device_class
await self.async_update()

async def set_unit(self, unit):
"""Set unit_of_measurement of entity"""
self._unit_of_measurement = unit
await self.async_update()
Loading