forked from platypush/platypush
Fabio Manganiello
d872835093
- Check if it's part of the metadata through a function call rather than checking `Base.metadata` in every single module. - Make it possible to override them (mostly for doc generation logic that needs to be able to import those classes). - Make it possible to extend them.
215 lines
6 KiB
Python
215 lines
6 KiB
Python
import json
|
|
import logging
|
|
from typing import Optional, Union
|
|
|
|
from sqlalchemy import (
|
|
Boolean,
|
|
Column,
|
|
Float,
|
|
ForeignKey,
|
|
Integer,
|
|
JSON,
|
|
String,
|
|
)
|
|
|
|
from platypush.common.db import is_defined
|
|
|
|
from .devices import Device
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Sensor(Device):
|
|
"""
|
|
Abstract class for sensor entities. A sensor entity is, by definition, an
|
|
entity with the ``is_read_only`` property set to ``True``.
|
|
"""
|
|
|
|
__abstract__ = True
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs['is_read_only'] = True
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
if not is_defined('raw_sensor'):
|
|
|
|
class RawSensor(Sensor):
|
|
"""
|
|
Models a raw sensor, whose value can contain either a string, a
|
|
hex-encoded binary string, or a JSON-encoded object.
|
|
"""
|
|
|
|
__tablename__ = 'raw_sensor'
|
|
|
|
id = Column(
|
|
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
|
|
)
|
|
_value = Column(String)
|
|
is_binary = Column(Boolean, default=False)
|
|
""" If ``is_binary`` is ``True``, then ``value`` is a hex string. """
|
|
is_json = Column(Boolean, default=False)
|
|
"""
|
|
If ``is_json`` is ``True``, then ``value`` is a JSON-encoded string
|
|
object or array.
|
|
"""
|
|
unit = Column(String)
|
|
|
|
@property
|
|
def value(self):
|
|
if self._value is None:
|
|
return None
|
|
if self.is_binary and isinstance(self._value, str):
|
|
value = self._value[2:]
|
|
return bytes(
|
|
[int(value[i : i + 2], 16) for i in range(0, len(value), 2)]
|
|
)
|
|
if self.is_json and isinstance(self._value, (str, bytes)):
|
|
return json.loads(self._value)
|
|
return self._value
|
|
|
|
@value.setter
|
|
def value(
|
|
self, value: Optional[Union[str, bytearray, bytes, list, tuple, set, dict]]
|
|
):
|
|
if isinstance(value, (bytearray, bytes)):
|
|
self._value = '0x' + ''.join([f'{x:02x}' for x in value])
|
|
self.is_binary = True
|
|
elif isinstance(value, (list, tuple, set)):
|
|
self._value = json.dumps(list(value))
|
|
self.is_json = True
|
|
elif isinstance(value, dict):
|
|
self._value = json.dumps(value)
|
|
self.is_json = True
|
|
else:
|
|
self._value = value
|
|
self.is_binary = False
|
|
self.is_json = False
|
|
|
|
__table_args__ = {'extend_existing': True}
|
|
__mapper_args__ = {
|
|
'polymorphic_identity': __tablename__,
|
|
}
|
|
|
|
|
|
if not is_defined('numeric_sensor') and not is_defined('percent_sensor'):
|
|
|
|
class NumericSensor(Sensor):
|
|
"""
|
|
Models a numeric sensor, with a numeric value and an optional min/max
|
|
range.
|
|
"""
|
|
|
|
__tablename__ = 'numeric_sensor'
|
|
|
|
id = Column(
|
|
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
|
|
)
|
|
value = Column(Float)
|
|
min = Column(Float)
|
|
max = Column(Float)
|
|
unit = Column(String)
|
|
|
|
__table_args__ = {'extend_existing': True}
|
|
__mapper_args__ = {
|
|
'polymorphic_identity': __tablename__,
|
|
}
|
|
|
|
class PercentSensor(NumericSensor):
|
|
"""
|
|
A subclass of ``NumericSensor`` that represents a percentage value.
|
|
"""
|
|
|
|
__tablename__ = 'percent_sensor'
|
|
|
|
id = Column(
|
|
Integer, ForeignKey(NumericSensor.id, ondelete='CASCADE'), primary_key=True
|
|
)
|
|
|
|
__table_args__ = {'extend_existing': True}
|
|
__mapper_args__ = {
|
|
'polymorphic_identity': __tablename__,
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.min = 0.0
|
|
self.max = 1.0
|
|
self.unit = '%'
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
if not is_defined('binary_sensor'):
|
|
|
|
class BinarySensor(Sensor):
|
|
"""
|
|
Models a binary sensor, with a binary boolean value.
|
|
"""
|
|
|
|
__tablename__ = 'binary_sensor'
|
|
|
|
def __init__(self, *args, value=None, **kwargs):
|
|
if isinstance(value, str):
|
|
value = value.lower()
|
|
|
|
if str(value).lower() in {'1', 't', 'true', 'on'}:
|
|
value = True
|
|
elif str(value).lower() in {'0', 'f', 'false', 'off'}:
|
|
value = False
|
|
elif value is not None:
|
|
logger.warning('Unsupported value for BinarySensor type: %s', value)
|
|
value = None
|
|
|
|
super().__init__(*args, value=value, **kwargs)
|
|
|
|
id = Column(
|
|
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
|
|
)
|
|
value = Column(Boolean)
|
|
|
|
__table_args__ = {'extend_existing': True}
|
|
__mapper_args__ = {
|
|
'polymorphic_identity': __tablename__,
|
|
}
|
|
|
|
|
|
if not is_defined('enum_sensor'):
|
|
|
|
class EnumSensor(Sensor):
|
|
"""
|
|
Models an enum sensor, whose value belongs to a set of pre-defined values.
|
|
"""
|
|
|
|
__tablename__ = 'enum_sensor'
|
|
|
|
id = Column(
|
|
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
|
|
)
|
|
value = Column(String)
|
|
values = Column(JSON)
|
|
""" Possible values for the sensor, as a JSON array. """
|
|
|
|
__table_args__ = {'extend_existing': True}
|
|
__mapper_args__ = {
|
|
'polymorphic_identity': __tablename__,
|
|
}
|
|
|
|
|
|
if not is_defined('composite_sensor'):
|
|
|
|
class CompositeSensor(Sensor):
|
|
"""
|
|
A composite sensor is a sensor whose value can be mapped to a JSON
|
|
object (either a dictionary or an array)
|
|
"""
|
|
|
|
__tablename__ = 'composite_sensor'
|
|
|
|
id = Column(
|
|
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
|
|
)
|
|
value = Column(JSON)
|
|
|
|
__table_args__ = {'extend_existing': True}
|
|
__mapper_args__ = {
|
|
'polymorphic_identity': __tablename__,
|
|
}
|