Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into dev

* 'dev' of https://github.com/home-assistant/home-assistant:
  Meraki AP Device tracker (home-assistant#10971)
  Update frontend to 20171206.0
  Allow disabling the LEDs on TP-Link smart plugs (home-assistant#10980)
  Revert pychromecast update (home-assistant#10989)
  Reload closest store on api menu request (home-assistant#10962)
  Allow chime to work for wink siren/chime (home-assistant#10961)
  Upgrade tellduslive library version (closes home-assistant#10922) (home-assistant#10950)
  Fix linksys_ap.py by inheriting DeviceScanner (home-assistant#10947)
  Require FF43 for latest js (home-assistant#10941)
  Version bump to 0.59.2
  Require FF43 for latest js (home-assistant#10941)
  Revert pychromecast update (home-assistant#10989)
  Allow chime to work for wink siren/chime (home-assistant#10961)
  Add option to set default hide if away for new devices (home-assistant#10762)
  Use new build path for dev translations (home-assistant#10937)
  Generic thermostat initial_operation_mode (home-assistant#10690)
  Add Ziggo Mediabox XL media_player (home-assistant#10514)
  Gearbest sensor (home-assistant#10556)
  Reload closest store on api menu request (home-assistant#10962)
  Add ADS component (home-assistant#10142)
  • Loading branch information
akatrevorjay committed Dec 6, 2017
2 parents 216074a + e66268d commit 0fe7ce5
Show file tree
Hide file tree
Showing 26 changed files with 1,341 additions and 48 deletions.
5 changes: 5 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/__init__.py
homeassistant/components/*/ads.py

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

Expand Down Expand Up @@ -427,6 +430,7 @@ omit =
homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/media_player/yamaha_musiccast.py
homeassistant/components/media_player/ziggo_mediabox_xl.py
homeassistant/components/mycroft.py
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
Expand Down Expand Up @@ -514,6 +518,7 @@ omit =
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gearbest.py
homeassistant/components/sensor/geizhals.py
homeassistant/components/sensor/gitter.py
homeassistant/components/sensor/glances.py
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ homeassistant/components/media_player/kodi.py @armills
homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen
homeassistant/components/sensor/sytadin.py @gautric
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

0 comments on commit 0fe7ce5

Please sign in to comment.