Homelab power control form the shell

In the first part we gain the ability to control the power switches in Home Assistant.

Now we can utilize the API to control the status of the power switch. For now i use the homeassistant_api in python, to do this. Below you can find an example.

Looking ahead, the vision includes functionalities for power management, accommodating various setups, preserving lab states, and simplifying state recovery. From my standpoint, it’s crucial to have the capability to schedule the lab’s operation based on specific timeframes or power it down when it’s idle. This scheduling feature proves especially beneficial when I’m away or in need of rest.

As time permits, I plan to reconfigure my setup to incorporate NetBox for documenting my homelab’s details, such as cabling, power outlets, and console or KVM connections.

Features like HP / Aruba Zero Touch Provisioning provides a way to configure the lab to a certain state or can easily be tested.

Example:

#!/usr/bin/env python
#
# Copyright 2023 Patrick Marc Preuss
#
import os
import sys
import yaml
import argparse
from homeassistant_api import Client
import logging

def get_config(filename):
    """Read the config file and return this."""
    fn = os.path.join(os.path.expanduser("~"), filename)
    config = None
    try:
        with open(fn, "r") as stream:
            try:
                config = yaml.safe_load(stream)
            except yaml.YAMLError as e:
                print(e)
                return None
    except FileNotFoundError as e:
        print(e)
        return None
    except IOError:
        print("io error")
        pass
    return config
 
def get_entity_id(config, device_name):
    """Get the entity_id for a device name form the config."""
    entity_ids = [
        d.get("entity_id")
        for d in config["devices"]
        if d.get("entity_id") and d.get("name") == device_name
    ]
    if not len(entity_ids) == 1:
        return None
    entity_id = entity_ids[0]
    return entity_id

def change_status(api_url, token, device_name, device_entity_id, state):
    """Change the status of en device (entity_id) and print the name."""
    with Client(
        api_url,
        token,
    ) as client:
        switches = client.get_domain("switch")
        if state == "on":
            switches.turn_on(entity_id=device_entity_id)
        elif state == "off":
            switches.turn_off(entity_id=device_entity_id)
        elif state == "toggle":
            switches.toggle(entity_id=device_entity_id)
        else:
            return None
    return state

def check_status(api_url, token, device_name, device_entity_id):
    """Check the status in homeassistant."""
    with Client(
        api_url,
        token,
    ) as client:
        device = client.get_entity(entity_id=device_entity_id)
        state = device.get_state()
    return state.state

def main(argv):
    """Do the Main function."""
    logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
    config = get_config(".power-config.yml")
    token = config["token"]
    api_url = config["api_url"]

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--device", help="The Device to change/check state.", required=True
    )
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        "--state",
        choices=["on", "off", "toggle"],
        help="new state on/off/toggle",
    )
    group.add_argument("--check", action="store_true", help="check the state")
    args = parser.parse_args()

    assert token is not None

    if not args.device:
        raise Exception("--device is required")

    device_name = args.device
    entity_id = get_entity_id(config, device_name)
    if (not args.state and not args.check) or args.check:
        # check_status(api_url, token, args.device)
        state = check_status(api_url, token, device_name, entity_id)
        logging.info("%s is %s", device_name, state)
        return 0

    state = change_status(api_url, token, device_name, entity_id, args.state)
    logging.info("%s is now %s", device_name, state)


if __name__ == "__main__":
    main(sys.argv[1:])

The config ~/.power-config.yml

api_url: http://:8123/api
token: 
devices:
    - name: coffee
      entity_id: switch.coffee_switch

License

Copyright 2023 Patrick Marc Preuss All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Photo by Thomas Kelley on Unsplash