Skip to content
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
48 changes: 48 additions & 0 deletions homeassistant/components/switch/zoneminder.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@

DEPENDENCIES = ['zoneminder']

DEFAULT_CAUSE = "Home Assistant Triggered Event"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND_ON): cv.string,
vol.Required(CONF_COMMAND_OFF): cv.string,
vol.Optional('ext_trigger_time', default=60): cv.time,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use constants.

vol.Optional('ext_trigger_enable', default=False): cv.boolean,
vol.Optional('ext_trigger_cause', default=DEFAULT_CAUSE): cv.string
})


Expand All @@ -40,6 +45,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
off_state
)
)
if config.get('ext_trigger_enable'):
switches.append(
ZMTriggerMonitors(
int(i['Monitor']['Id']),
i['Monitor']['Name'],
config.get('ext_trigger_cause'),
config.get('ext_trigger_time')
)
)

add_devices(switches)

Expand Down Expand Up @@ -88,3 +102,37 @@ def turn_off(self):
'api/monitors/%i.json' % self._monitor_id,
{'Monitor[Function]': self._off_state}
)


class ZMTriggerMonitors(SwitchDevice):
"""Representation of External Trigger."""

def __init__(self, monitor_id, monitor_name, cause, timeout):
"""Initialize the switch."""
self._monitor_id = monitor_id
self._monitor_name = monitor_name
self._ison = None
self._cause = cause
self._timeout = timeout

@property
def is_on(self):
"""Return True if entity is on."""
return self._ison

def turn_on(self):
"""Turn the entity on."""
zoneminder.zm_trigger(
self._monitor_id, "on", self._cause, self._timeout)
self._ison = True

def turn_off(self):
"""Turn the entity off."""
zoneminder.zm_trigger(
self._monitor_id, "off", self._cause, self._timeout)
self._ison = False

@property
def name(self):
"""Return the name of the switch."""
return "%s Trigger" % self._monitor_name
20 changes: 20 additions & 0 deletions homeassistant/components/zoneminder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
DEFAULT_PATH_ZMS = '/zm/cgi-bin/nph-zms'
DEFAULT_SSL = False
DEFAULT_TIMEOUT = 10
DEFAULT_TRIGGER_PORT = 6802
DOMAIN = 'zoneminder'

LOGIN_RETRIES = 2
Expand All @@ -32,6 +33,7 @@
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional('trigger_port', default=DEFAULT_TRIGGER_PORT): cv.port,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a constant.

# This should match PATH_ZMS in ZoneMinder settings.
vol.Optional(CONF_PATH_ZMS, default=DEFAULT_PATH_ZMS): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
Expand Down Expand Up @@ -61,6 +63,8 @@ def setup(hass, config):
ZM['username'] = username
ZM['password'] = password
ZM['path_zms'] = conf.get(CONF_PATH_ZMS)
ZM['host'] = conf[CONF_HOST]
ZM['trigger_port'] = conf['trigger_port']

hass.data[DOMAIN] = ZM

Expand Down Expand Up @@ -118,6 +122,22 @@ def _zm_request(method, api_url, data=None):
'decode "%s"', req.text)


def zm_trigger(monitor, state, cause, duration):
"""Attempt to connect over TCP socket and trigger recording."""
import socket
sock = socket.socket()
try:
sock.connect((ZM['host'], ZM['trigger_port']))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is crossing the boundary of protocol specific code that is allowed to be in Home Assistant. Please extract the Zoneminder logic into it's own Python library.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean to shove all that into PyPi package?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

command = '{:d}|{:s}'.format(monitor, state)
if state in 'on':
command += "+{:d}|1|{:s}|{:s}".format(duration, cause, cause)
sock.sendall(command.encode())
except socket.error as sock_err:
_LOGGER.exception('Connect failed (%s), check OPT_TRIGGERS', sock_err)
finally:
sock.close()


# pylint: disable=no-member
def get_state(api_url):
"""Get a state from the ZoneMinder API service."""
Expand Down