-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
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
Add ADS component #10142
Merged
Merged
Add ADS component #10142
Changes from 40 commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
7f74ab2
add ads hub, light and switch
4a09d7a
add binary sensor prototype
12a87c3
switch: use adsvar for connection
stlehmann 3d69f45
fix some issues with binary sensor
stlehmann 66a5226
fix binary sensor
stlehmann 429d5b3
fix all platforms
stlehmann aefaf31
use latest pyads
6143c77
fixed error with multiple binary sensors
7aa7ce2
add sensor
1430213
Merge remote-tracking branch 'upstream/dev' into dev
79e4112
add ads sensor
af9f90f
clean up after shutdown
05a3f63
Merge remote-tracking branch 'upstream/dev' into dev
41bbcf0
ads component with platforms switch, binary_sensor, light, sensor
866cbb2
add ads service
14c0552
add default settings for use_notify and poll_interval
94bc9b3
fix too long line
10a7772
Fix style issues
b0999f4
no pydocstyle errors
0c3b28c
Send and receive native brightness data to ADS device to prevent issu…
carstenschroeder 4631da4
Merge pull request #1 from carstenschroeder/patch-3
da54023
Enable non dimmable lights
carstenschroeder f6024a3
remove setting of self._state in switch
6de2c6b
Merge branch 'ads' of https://github.com/MrLeeh/home-assistant into ads
7da420f
remove polling
b3e1d2a
Revert "remove polling"
b66c2f6
add service schema, add links to documentation
ab4a6a9
fix naming, cleanup
6eb8926
re-remove polling
b655182
use async_added_to_hass for setup of callbacks
2589696
fix comment.
0b15de4
add callbacks for changed values
50fe60f
use async_add_job for creating device notifications
45f5c9f
set should_poll to False for all platforms
aaa2a8c
change should_poll to property
123f28a
add service description to services.yaml
5708793
Merge branch 'dev' into ads
126e90b
add for brigthness not being None
1972b9d
Merge branch 'patch-1' of git://github.com/carstenschroeder/home-assi…
c2dbc2d
put ads component in package
04980ec
Remove whitespace
5befcea
omit ads package
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
""" | ||
ADS Component. | ||
|
||
For more details about this component, please refer to the documentation. | ||
https://home-assistant.io/components/ads/ | ||
|
||
""" | ||
import os | ||
import threading | ||
import struct | ||
import logging | ||
import ctypes | ||
from collections import namedtuple | ||
import voluptuous as vol | ||
from homeassistant.const import CONF_DEVICE, CONF_PORT, CONF_IP_ADDRESS, \ | ||
EVENT_HOMEASSISTANT_STOP | ||
from homeassistant.config import load_yaml_config_file | ||
import homeassistant.helpers.config_validation as cv | ||
|
||
REQUIREMENTS = ['pyads==2.2.6'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DATA_ADS = 'data_ads' | ||
|
||
# Supported Types | ||
ADSTYPE_INT = 'int' | ||
ADSTYPE_UINT = 'uint' | ||
ADSTYPE_BYTE = 'byte' | ||
ADSTYPE_BOOL = 'bool' | ||
|
||
DOMAIN = 'ads' | ||
|
||
# config variable names | ||
CONF_ADS_VAR = 'adsvar' | ||
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness' | ||
CONF_ADS_TYPE = 'adstype' | ||
CONF_ADS_FACTOR = 'factor' | ||
CONF_ADS_VALUE = 'value' | ||
|
||
SERVICE_WRITE_DATA_BY_NAME = 'write_data_by_name' | ||
|
||
CONFIG_SCHEMA = vol.Schema({ | ||
DOMAIN: vol.Schema({ | ||
vol.Required(CONF_DEVICE): cv.string, | ||
vol.Required(CONF_PORT): cv.port, | ||
vol.Optional(CONF_IP_ADDRESS): cv.string, | ||
}) | ||
}, extra=vol.ALLOW_EXTRA) | ||
|
||
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({ | ||
vol.Required(CONF_ADS_VAR): cv.string, | ||
vol.Required(CONF_ADS_TYPE): vol.In([ADSTYPE_INT, ADSTYPE_UINT, | ||
ADSTYPE_BYTE]), | ||
vol.Required(CONF_ADS_VALUE): cv.match_all | ||
}) | ||
|
||
|
||
def setup(hass, config): | ||
"""Set up the ADS component.""" | ||
import pyads | ||
conf = config[DOMAIN] | ||
|
||
# get ads connection parameters from config | ||
net_id = conf.get(CONF_DEVICE) | ||
ip_address = conf.get(CONF_IP_ADDRESS) | ||
port = conf.get(CONF_PORT) | ||
|
||
# create a new ads connection | ||
client = pyads.Connection(net_id, port, ip_address) | ||
|
||
# add some constants to AdsHub | ||
AdsHub.ADS_TYPEMAP = { | ||
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL, | ||
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE, | ||
ADSTYPE_INT: pyads.PLCTYPE_INT, | ||
ADSTYPE_UINT: pyads.PLCTYPE_UINT, | ||
} | ||
|
||
AdsHub.PLCTYPE_BOOL = pyads.PLCTYPE_BOOL | ||
AdsHub.PLCTYPE_BYTE = pyads.PLCTYPE_BYTE | ||
AdsHub.PLCTYPE_INT = pyads.PLCTYPE_INT | ||
AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT | ||
AdsHub.ADSError = pyads.ADSError | ||
|
||
# connect to ads client and try to connect | ||
try: | ||
ads = AdsHub(client) | ||
except pyads.pyads.ADSError: | ||
_LOGGER.error( | ||
'Could not connect to ADS host (netid=%s, port=%s)', net_id, port | ||
) | ||
return False | ||
|
||
# add ads hub to hass data collection, listen to shutdown | ||
hass.data[DATA_ADS] = ads | ||
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, ads.shutdown) | ||
|
||
def handle_write_data_by_name(call): | ||
"""Write a value to the connected ADS device.""" | ||
ads_var = call.data.get(CONF_ADS_VAR) | ||
ads_type = call.data.get(CONF_ADS_TYPE) | ||
value = call.data.get(CONF_ADS_VALUE) | ||
|
||
try: | ||
ads.write_by_name(ads_var, value, ads.ADS_TYPEMAP[ads_type]) | ||
except pyads.ADSError as err: | ||
_LOGGER.error(err) | ||
|
||
# load descriptions from services.yaml | ||
descriptions = load_yaml_config_file( | ||
os.path.join(os.path.dirname(__file__), 'services.yaml')) | ||
|
||
hass.services.register( | ||
DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name, | ||
descriptions[SERVICE_WRITE_DATA_BY_NAME], | ||
schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME | ||
) | ||
|
||
return True | ||
|
||
|
||
# tuple to hold data needed for notification | ||
NotificationItem = namedtuple( | ||
'NotificationItem', 'hnotify huser name plc_datatype callback' | ||
) | ||
|
||
|
||
class AdsHub: | ||
"""Representation of a PyADS connection.""" | ||
|
||
def __init__(self, ads_client): | ||
"""Initialize the ADS Hub.""" | ||
self._client = ads_client | ||
self._client.open() | ||
|
||
# all ADS devices are registered here | ||
self._devices = [] | ||
self._notification_items = {} | ||
self._lock = threading.Lock() | ||
|
||
def shutdown(self, *args, **kwargs): | ||
"""Shutdown ADS connection.""" | ||
_LOGGER.debug('Shutting down ADS') | ||
for notification_item in self._notification_items.values(): | ||
self._client.del_device_notification( | ||
notification_item.hnotify, | ||
notification_item.huser | ||
) | ||
_LOGGER.debug( | ||
'Deleting device notification %d, %d', | ||
notification_item.hnotify, notification_item.huser | ||
) | ||
self._client.close() | ||
|
||
def register_device(self, device): | ||
"""Register a new device.""" | ||
self._devices.append(device) | ||
|
||
def write_by_name(self, name, value, plc_datatype): | ||
"""Write a value to the device.""" | ||
with self._lock: | ||
return self._client.write_by_name(name, value, plc_datatype) | ||
|
||
def read_by_name(self, name, plc_datatype): | ||
"""Read a value from the device.""" | ||
with self._lock: | ||
return self._client.read_by_name(name, plc_datatype) | ||
|
||
def add_device_notification(self, name, plc_datatype, callback): | ||
"""Add a notification to the ADS devices.""" | ||
from pyads import NotificationAttrib | ||
attr = NotificationAttrib(ctypes.sizeof(plc_datatype)) | ||
|
||
with self._lock: | ||
hnotify, huser = self._client.add_device_notification( | ||
name, attr, self._device_notification_callback | ||
) | ||
hnotify = int(hnotify) | ||
|
||
_LOGGER.debug( | ||
'Added Device Notification %d for variable %s', hnotify, name | ||
) | ||
|
||
self._notification_items[hnotify] = NotificationItem( | ||
hnotify, huser, name, plc_datatype, callback | ||
) | ||
|
||
def _device_notification_callback(self, addr, notification, huser): | ||
"""Handle device notifications.""" | ||
contents = notification.contents | ||
|
||
hnotify = int(contents.hNotification) | ||
_LOGGER.debug('Received Notification %d', hnotify) | ||
data = contents.data | ||
|
||
try: | ||
notification_item = self._notification_items[hnotify] | ||
except KeyError: | ||
_LOGGER.debug('Unknown Device Notification handle: %d', hnotify) | ||
return | ||
|
||
# parse data to desired datatype | ||
if notification_item.plc_datatype == self.PLCTYPE_BOOL: | ||
value = bool(struct.unpack('<?', bytearray(data)[:1])[0]) | ||
elif notification_item.plc_datatype == self.PLCTYPE_INT: | ||
value = struct.unpack('<h', bytearray(data)[:2])[0] | ||
elif notification_item.plc_datatype == self.PLCTYPE_BYTE: | ||
value = struct.unpack('<B', bytearray(data)[:1])[0] | ||
elif notification_item.plc_datatype == self.PLCTYPE_UINT: | ||
value = struct.unpack('<H', bytearray(data)[:2])[0] | ||
else: | ||
value = bytearray(data) | ||
_LOGGER.warning('No callback available for this datatype.') | ||
|
||
# execute callback | ||
notification_item.callback(notification_item.name, value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Describes the format for available ADS services | ||
|
||
write_data_by_name: | ||
description: Write a value to the connected ADS device. | ||
|
||
fields: | ||
adsvar: | ||
description: The name of the variable to write to. | ||
example: '.global_var' | ||
adstype: | ||
description: The data type of the variable to write to. | ||
example: 'int' | ||
value: | ||
description: The value to write to the variable. | ||
example: 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
""" | ||
Support for ADS binary sensors. | ||
|
||
For more details about this platform, please refer to the documentation. | ||
https://home-assistant.io/components/binary_sensor.ads/ | ||
|
||
""" | ||
import asyncio | ||
import logging | ||
import voluptuous as vol | ||
from homeassistant.components.binary_sensor import BinarySensorDevice, \ | ||
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA | ||
from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR | ||
from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS | ||
import homeassistant.helpers.config_validation as cv | ||
|
||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DEPENDENCIES = ['ads'] | ||
DEFAULT_NAME = 'ADS binary sensor' | ||
|
||
|
||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
vol.Required(CONF_ADS_VAR): cv.string, | ||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, | ||
}) | ||
|
||
|
||
def setup_platform(hass, config, add_devices, discovery_info=None): | ||
"""Set up the Binary Sensor platform for ADS.""" | ||
ads_hub = hass.data.get(DATA_ADS) | ||
|
||
ads_var = config.get(CONF_ADS_VAR) | ||
name = config.get(CONF_NAME) | ||
device_class = config.get(CONF_DEVICE_CLASS) | ||
|
||
ads_sensor = AdsBinarySensor(ads_hub, name, ads_var, device_class) | ||
add_devices([ads_sensor]) | ||
|
||
|
||
class AdsBinarySensor(BinarySensorDevice): | ||
"""Representation of ADS binary sensors.""" | ||
|
||
def __init__(self, ads_hub, name, ads_var, device_class): | ||
"""Initialize AdsBinarySensor entity.""" | ||
self._name = name | ||
self._state = False | ||
self._device_class = device_class or 'moving' | ||
self._ads_hub = ads_hub | ||
self.ads_var = ads_var | ||
|
||
@asyncio.coroutine | ||
def async_added_to_hass(self): | ||
"""Register device notification.""" | ||
def update(name, value): | ||
"""Handle device notifications.""" | ||
_LOGGER.debug('Variable %s changed its value to %d', | ||
name, value) | ||
self._state = value | ||
self.schedule_update_ha_state() | ||
|
||
self.hass.async_add_job( | ||
self._ads_hub.add_device_notification, | ||
self.ads_var, self._ads_hub.PLCTYPE_BOOL, update | ||
) | ||
|
||
@property | ||
def name(self): | ||
"""Return the default name of the binary sensor.""" | ||
return self._name | ||
|
||
@property | ||
def device_class(self): | ||
"""Return the device class.""" | ||
return self._device_class | ||
|
||
@property | ||
def is_on(self): | ||
"""Return if the binary sensor is on.""" | ||
return self._state | ||
|
||
@property | ||
def should_poll(self): | ||
"""Return False because entity pushes its state to HA.""" | ||
return False |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to update the path to the package here. Sorry that I missed that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn, I forgot about this. Should work now.