/ IoT

How to get a PC integrated into your HomeAutomation

In my constant effort to integrate more stuff into my home automation setup, one thing was bugging my for some time: The A/V stuff as well as the ambient lighting in my living room was already integrated, but not the damn PC. I even got the projector with its proprietary connector and an ESP8266 integrated.

Once the PC is on, I can control it thanks to OpenHAB's Kodi binding. But turning the PC on was a problem, because it only has WiFi: Goodbye WakeOnLAN. I already had something in mind: use an ESP8266 to "press" the power and reset buttons.

Last weekend I had my final break through: why not hack my own RGB LED strip controller? I had some boards lying around and they have three FETs. Two to push the buttons and with some trace cutting and rewiring I can even monitor the power LED to stay in sync in case the real buttons are pushed. Its powered by the standby power rail of the ATX power supply.

pc_ctrl_installed

I published the LED strip controller board on OSHpark. If you are interested in the changes I made, please let me know.

I was a bit lazy on the software side. Usually I prefer my own custom-fit firmware, but this time I tried tasmota and ESPEasy. In the end, I went with ESPEasy, because it worked better in this scenario (at least for me).

Integration into OpenHAB was another experiment: I wanted to use the Jython rule engine. All my other customizations are running with the internal rule language, but I find that a bit inflexible at times due to the lack of encapsulation. Jython was a good fit: the integration is fully encapsulated within its own class. The control and status items that link the rules to the hardware are passed in on initialization.

Here's the Jython code. It is running on OpenHAB 2.2; I think the trigger creation has slightly changed on newer versions, but I haven't had time to update yet.

scriptExtension.importPreset("RuleSupport")
scriptExtension.importPreset("RuleSimple")

from org.slf4j import Logger, LoggerFactory
log = LoggerFactory.getLogger("org.eclipse.smarthome.automation.pc_ctrl")

log.info("Loading pc_ctrl rules.")

from org.eclipse.smarthome.automation import Rule as SmarthomeRule
from org.eclipse.smarthome.core.types import UnDefType
from org.eclipse.smarthome.core.library.types import IncreaseDecreaseType, NextPreviousType, OnOffType, OpenClosedType, PlayPauseType, RewindFastforwardType, StopMoveType, UpDownType, DecimalType

import time

class PcCtrlRule(SimpleRule):
    _pulse_cmd_pwr = 'Pulse,14,1,500'
    _pulse_cmd_rst = 'Pulse,12,1,500'
    _pulse_sleep = 0.5

    def __init__(self, name, cmd_item_name, on_state_item_name, ctrl_item_name):
        self._name = name
        self._cmd_item_name = cmd_item_name
        self._cmd_item = itemRegistry.getItem(cmd_item_name)
        self._on_state_item_name = on_state_item_name
        self._on_state_item = itemRegistry.getItem(on_state_item_name)
        self._ctrl_item_name = ctrl_item_name
        self._ctrl_item = itemRegistry.getItem(ctrl_item_name)

        # create the triggers
        trigger_on_state = Trigger('{}OnStateTrigger'.format(name),
                                   'core.ItemStateChangeTrigger',
                                   Configuration({'itemName': on_state_item_name}))

        trigger_ctrl = Trigger('{}CtrlTrigger'.format(name),
                                   'core.ItemCommandTrigger',
                                   Configuration({'itemName': ctrl_item_name}))

        # register them
        self.triggers = [trigger_on_state, trigger_ctrl]

    def execute(self, module, inputs):
        event = inputs.get('event')
        command = inputs.get('command')
        trig_item = itemRegistry.getItem(event.itemName)
        trig_item_state = trig_item.state

        log.debug('inputs=\n{}'.format(inputs))

        if event.itemName == self._on_state_item_name:
            log.debug('on_state_item changed')
            log.debug('oldItemState={}, itemState={}'.format(event.oldItemState, event.itemState))
            log.debug('trig_item_state={}'.format(trig_item_state))

            if (event.oldItemState == UnDefType.UNDEF) or (event.itemState != event.oldItemState):
                if str(event.itemState) == str(ON):
                    log.debug('Going ON')
                    events.postUpdate(self._ctrl_item, ON)
                else:
                    log.debug('Going OFF')
                    events.postUpdate(self._ctrl_item, OFF)

        elif event.itemName == self._ctrl_item_name:
            log.debug('ctrl_item cmd received')

            on_item_state = self._on_state_item.state
            cmd_to_send = ''

            log.debug('currentState={}'.format(on_item_state))
            log.debug('command={}'.format(command))

            if (str(on_item_state) == str(OFF)) and (str(command) == str(ON)):
                log.debug('Switching ON')
                cmd_to_send = self._pulse_cmd_pwr
            elif (str(on_item_state) == str(ON)) and (str(command) == str(OFF)):
                log.debug('Switching OFF')
                cmd_to_send = self._pulse_cmd_pwr

            if cmd_to_send != '':
                log.debug('Sending cmd: \'{}\''.format(cmd_to_send))
                events.sendCommand(self._cmd_item, cmd_to_send)
                time.sleep(self._pulse_sleep)
                events.sendCommand(self._cmd_item, '')

automationManager.addRule(PcCtrlRule('WZ_Watt_control_', 'WZ_Watt_control_cmd', 'WZ_Watt_control_pwr_state', 'WZ_Watt_Power'))

And the corresponding item definitions:

Switch WZ_Watt_Power "watt" <player> (gWZ)
String WZ_Watt_control_cmd "watt control command" (gInternal) { mqtt=">[home:whan/pc_ctrl/xxxxxxxx/cmd:command:*:default]" }
String WZ_Watt_control_pwr_state "watt control power state" (gInternal) { mqtt="<[home:whan/pc_ctrl/xxxxxxxx/pc_on/state:state:MAP(ONOFF_to_01.map)]" }

Please note that I changed the topic structure a bit from the defaults. It matches my other devices better this way.