From 63c9d59d5455850fd4b37c2475fe6f10effb5245 Mon Sep 17 00:00:00 2001 From: Georgi Kirichkov Date: Wed, 25 Oct 2017 17:09:29 +0300 Subject: [PATCH] SNMP switch (#9840) * Initial support for SNMP switch Supports setting integer values * Resolves styling issues * Updates requirements * Remove commented out import * Changes default community to private. Fixes linting errors. * More linter fixes and bugfix in #state() * Refactors the code to make it simpler. --- .coveragerc | 1 + homeassistant/components/switch/snmp.py | 150 ++++++++++++++++++++++++ requirements_all.txt | 1 + 3 files changed, 152 insertions(+) create mode 100644 homeassistant/components/switch/snmp.py diff --git a/.coveragerc b/.coveragerc index 522ba69f08c..58e1293ba28 100644 --- a/.coveragerc +++ b/.coveragerc @@ -599,6 +599,7 @@ omit = homeassistant/components/switch/rainmachine.py homeassistant/components/switch/rest.py homeassistant/components/switch/rpi_rf.py + homeassistant/components/switch/snmp.py homeassistant/components/switch/tplink.py homeassistant/components/switch/telnet.py homeassistant/components/switch/transmission.py diff --git a/homeassistant/components/switch/snmp.py b/homeassistant/components/switch/snmp.py new file mode 100644 index 00000000000..dc165661daf --- /dev/null +++ b/homeassistant/components/switch/snmp.py @@ -0,0 +1,150 @@ +""" +Support for SNMP enabled switch. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.snmp/ +""" + +import logging + +import voluptuous as vol + +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PORT, + CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['pysnmp==4.3.10'] + +_LOGGER = logging.getLogger(__name__) + +CONF_BASEOID = 'baseoid' +CONF_COMMUNITY = 'community' +CONF_VERSION = 'version' + +DEFAULT_NAME = 'SNMPSwitch' +DEFAULT_HOST = 'localhost' +DEFAULT_PORT = '161' +DEFAULT_COMMUNITY = 'private' +DEFAULT_VERSION = '1' +DEFAULT_PAYLOAD_ON = 1 +DEFAULT_PAYLOAD_OFF = 0 + +SNMP_VERSIONS = { + '1': 0, + '2c': 1 +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_BASEOID): cv.string, + vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): + vol.In(SNMP_VERSIONS), + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the SNMP switch.""" + name = config.get(CONF_NAME) + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + community = config.get(CONF_COMMUNITY) + baseoid = config.get(CONF_BASEOID) + version = config.get(CONF_VERSION) + payload_on = config.get(CONF_PAYLOAD_ON) + payload_off = config.get(CONF_PAYLOAD_OFF) + + add_devices([SnmpSwitch(name, host, port, community, + baseoid, version, payload_on, + payload_off)], True) + + +class SnmpSwitch(SwitchDevice): + """Represents a SNMP switch.""" + + def __init__(self, name, host, port, community, + baseoid, version, payload_on, payload_off): + """Initialize the switch.""" + self._name = name + self._host = host + self._port = port + self._community = community + self._baseoid = baseoid + self._version = SNMP_VERSIONS[version] + self._state = None + self._payload_on = payload_on + self._payload_off = payload_off + + def turn_on(self): + """Turn on the switch.""" + from pyasn1.type.univ import (Integer) + + self._set(Integer(self._payload_on)) + + def turn_off(self): + """Turn off the switch.""" + from pyasn1.type.univ import (Integer) + + self._set(Integer(self._payload_off)) + + def update(self): + """Update the state.""" + from pysnmp.hlapi import ( + getCmd, CommunityData, SnmpEngine, UdpTransportTarget, ContextData, + ObjectType, ObjectIdentity) + + from pyasn1.type.univ import (Integer) + + request = getCmd( + SnmpEngine(), + CommunityData(self._community, mpModel=self._version), + UdpTransportTarget((self._host, self._port)), + ContextData(), + ObjectType(ObjectIdentity(self._baseoid)) + ) + + errindication, errstatus, errindex, restable = next(request) + + if errindication: + _LOGGER.error("SNMP error: %s", errindication) + elif errstatus: + _LOGGER.error("SNMP error: %s at %s", errstatus.prettyPrint(), + errindex and restable[-1][int(errindex) - 1] or '?') + else: + for resrow in restable: + if resrow[-1] == Integer(self._payload_on): + self._state = True + elif resrow[-1] == Integer(self._payload_off): + self._state = False + else: + self._state = None + + @property + def name(self): + """Return the switch's name.""" + return self._name + + @property + def is_on(self): + """Return true if switch is on; False if off. None if unknown.""" + return self._state + + def _set(self, value): + from pysnmp.hlapi import ( + setCmd, CommunityData, SnmpEngine, UdpTransportTarget, ContextData, + ObjectType, ObjectIdentity) + + request = setCmd( + SnmpEngine(), + CommunityData(self._community, mpModel=self._version), + UdpTransportTarget((self._host, self._port)), + ContextData(), + ObjectType(ObjectIdentity(self._baseoid), value) + ) + + next(request) diff --git a/requirements_all.txt b/requirements_all.txt index 720cadcdc52..3d899ecbee7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -764,6 +764,7 @@ pysma==0.1.3 # homeassistant.components.device_tracker.snmp # homeassistant.components.sensor.snmp +# homeassistant.components.switch.snmp pysnmp==4.3.10 # homeassistant.components.sensor.thinkingcleaner