diff --git a/.coveragerc b/.coveragerc index 173d6ab4865..153d0182f62 100644 --- a/.coveragerc +++ b/.coveragerc @@ -165,6 +165,7 @@ omit = homeassistant/components/sensor/twitch.py homeassistant/components/sensor/uber.py homeassistant/components/sensor/worldclock.py + homeassistant/components/switch/acer_projector.py homeassistant/components/switch/arest.py homeassistant/components/switch/dlink.py homeassistant/components/switch/edimax.py diff --git a/homeassistant/components/switch/acer_projector.py b/homeassistant/components/switch/acer_projector.py new file mode 100644 index 00000000000..e6e1eb76b75 --- /dev/null +++ b/homeassistant/components/switch/acer_projector.py @@ -0,0 +1,145 @@ +""" +Use serial protocol of acer projector to obtain state of the projector. + +This component allows to control almost all projectors from acer using +their RS232 serial communication protocol. +""" + +import logging +import re + +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN, + CONF_NAME, CONF_FILENAME) + +LAMP_HOURS = 'Lamp Hours' +INPUT_SOURCE = 'Input Source' +ECO_MODE = 'ECO Mode' +MODEL = 'Model' +LAMP = 'Lamp' + +# Commands known to the projector +CMD_DICT = {LAMP: '* 0 Lamp ?\r', + LAMP_HOURS: '* 0 Lamp\r', + INPUT_SOURCE: '* 0 Src ?\r', + ECO_MODE: '* 0 IR 052\r', + MODEL: '* 0 IR 035\r', + STATE_ON: '* 0 IR 001\r', + STATE_OFF: '* 0 IR 002\r'} + +_LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ['pyserial<=3.0'] + +ICON = 'mdi:projector' + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Connect with serial port and return Acer Projector.""" + serial_port = config.get(CONF_FILENAME, None) + name = config.get(CONF_NAME, 'Projector') + timeout = config.get('timeout', 1) + write_timeout = config.get('write_timeout', 1) + + if not serial_port: + _LOGGER.error('Missing path of serial device') + return + + devices = [] + devices.append(AcerSwitch(serial_port, name, timeout, write_timeout)) + add_devices_callback(devices) + + +class AcerSwitch(SwitchDevice): + """Represents an Acer Projector as an switch.""" + + def __init__(self, serial_port, name='Projector', + timeout=1, write_timeout=1, **kwargs): + """Init of the Acer projector.""" + import serial + self.ser = serial.Serial(port=serial_port, timeout=timeout, + write_timeout=write_timeout, **kwargs) + self._serial_port = serial_port + self._name = name + self._state = STATE_UNKNOWN + self._attributes = { + LAMP_HOURS: STATE_UNKNOWN, + INPUT_SOURCE: STATE_UNKNOWN, + ECO_MODE: STATE_UNKNOWN, + } + self.update() + + def _write_read(self, msg): + """Write to the projector and read the return.""" + import serial + ret = "" + # Sometimes the projector won't answer for no reason, + # or the projector was disconnected during runtime. + # Thisway the projector can be reconnected and will still + # work + try: + if not self.ser.is_open: + self.ser.open() + msg = msg.encode('utf-8') + self.ser.write(msg) + # size is an experience value there is no real limit. + # AFAIK there is no limit and no end character so + # we will usually need to wait for timeout + ret = self.ser.read_until(size=20).decode('utf-8') + except serial.SerialException: + _LOGGER.error('Problem comunicating with %s', self._serial_port) + self.ser.close() + return ret + + def _write_read_format(self, msg): + """Write msg, obtain awnser and format output.""" + # awnsers are formated as ***\rawnser\r*** + awns = self._write_read(msg) + match = re.search(r'\r(.+)\r', awns) + if match: + return match.group(1) + return STATE_UNKNOWN + + @property + def name(self): + """Return name of the projector.""" + return self._name + + @property + def state(self): + """Return the current state of the projector.""" + return self._state + + @property + def state_attributes(self): + """Return state attributes.""" + return self._attributes + + def update(self): + """Get the latest state from the projector.""" + msg = CMD_DICT[LAMP] + awns = self._write_read_format(msg) + if awns == 'Lamp 1': + self._state = STATE_ON + elif awns == 'Lamp 0': + self._state = STATE_OFF + else: + self._state = STATE_UNKNOWN + + for key in self._attributes.keys(): + msg = CMD_DICT.get(key, None) + if msg: + awns = self._write_read_format(msg) + self._attributes[key] = awns + + def turn_on(self): + """Turn the projector on.""" + msg = CMD_DICT[STATE_ON] + self._write_read(msg) + self._state = STATE_ON + + def turn_off(self): + """Turn the projector off.""" + msg = CMD_DICT[STATE_OFF] + self._write_read(msg) + self._state = STATE_OFF diff --git a/requirements_all.txt b/requirements_all.txt index 8550b0d7429..082166b5a99 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -221,6 +221,9 @@ pynx584==0.2 # homeassistant.components.sensor.openweathermap pyowm==2.3.1 +# homeassistant.components.switch.acer_projector +pyserial<=3.0 + # homeassistant.components.device_tracker.snmp pysnmp==4.2.5