Skip to content
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 42 commits into from
Dec 5, 2017
Merged
Show file tree
Hide file tree
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
Sep 15, 2017
4a09d7a
add binary sensor prototype
Sep 24, 2017
12a87c3
switch: use adsvar for connection
stlehmann Sep 25, 2017
3d69f45
fix some issues with binary sensor
stlehmann Sep 25, 2017
66a5226
fix binary sensor
stlehmann Sep 26, 2017
429d5b3
fix all platforms
stlehmann Sep 26, 2017
aefaf31
use latest pyads
Sep 29, 2017
6143c77
fixed error with multiple binary sensors
Oct 1, 2017
7aa7ce2
add sensor
Oct 1, 2017
1430213
Merge remote-tracking branch 'upstream/dev' into dev
Oct 1, 2017
79e4112
add ads sensor
Oct 2, 2017
af9f90f
clean up after shutdown
Oct 7, 2017
05a3f63
Merge remote-tracking branch 'upstream/dev' into dev
Oct 8, 2017
41bbcf0
ads component with platforms switch, binary_sensor, light, sensor
Oct 8, 2017
866cbb2
add ads service
Oct 23, 2017
14c0552
add default settings for use_notify and poll_interval
Oct 24, 2017
94bc9b3
fix too long line
Oct 25, 2017
10a7772
Fix style issues
Oct 27, 2017
b0999f4
no pydocstyle errors
Oct 27, 2017
0c3b28c
Send and receive native brightness data to ADS device to prevent issu…
carstenschroeder Nov 9, 2017
4631da4
Merge pull request #1 from carstenschroeder/patch-3
Nov 9, 2017
da54023
Enable non dimmable lights
carstenschroeder Nov 10, 2017
f6024a3
remove setting of self._state in switch
Nov 16, 2017
6de2c6b
Merge branch 'ads' of https://github.com/MrLeeh/home-assistant into ads
Nov 16, 2017
7da420f
remove polling
Nov 18, 2017
b3e1d2a
Revert "remove polling"
Nov 19, 2017
b66c2f6
add service schema, add links to documentation
Nov 19, 2017
ab4a6a9
fix naming, cleanup
Nov 19, 2017
6eb8926
re-remove polling
Nov 21, 2017
b655182
use async_added_to_hass for setup of callbacks
Nov 29, 2017
2589696
fix comment.
Nov 29, 2017
0b15de4
add callbacks for changed values
Dec 1, 2017
50fe60f
use async_add_job for creating device notifications
Dec 1, 2017
45f5c9f
set should_poll to False for all platforms
Dec 1, 2017
aaa2a8c
change should_poll to property
Dec 1, 2017
123f28a
add service description to services.yaml
Dec 3, 2017
5708793
Merge branch 'dev' into ads
Dec 3, 2017
126e90b
add for brigthness not being None
Dec 3, 2017
1972b9d
Merge branch 'patch-1' of git://github.com/carstenschroeder/home-assi…
Dec 3, 2017
c2dbc2d
put ads component in package
Dec 3, 2017
04980ec
Remove whitespace
Dec 3, 2017
5befcea
omit ads package
Dec 5, 2017
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
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ omit =
homeassistant/components/abode.py
homeassistant/components/*/abode.py

homeassistant/components/ads.py
Copy link
Member

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.

Copy link
Contributor Author

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.

homeassistant/components/*/ads.py

homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py

Expand Down
217 changes: 217 additions & 0 deletions homeassistant/components/ads/__init__.py
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)
15 changes: 15 additions & 0 deletions homeassistant/components/ads/services.yaml
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
87 changes: 87 additions & 0 deletions homeassistant/components/binary_sensor/ads.py
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
Loading