Tuesday, March 21, 2023

Learnings when creating a new Home Assistant component for Maginon smart plugs (Edimax, ST Electronics like)

Introduction

I have a handful of smart plugs from Maginon (SP-1E) at home. They are smart plugs that can be switched and measure the power consumption (and Energy consumption). There is no component available for Home Assistant, but when browsing in the integrations, I saw that an Edimax integration is there. Since the Maginon smart plugs are similar to one type of the Edimax SP-1101 ones, I thought integration would be easy... Now I know better. :-) I am not a programmer, but have an analytic mind and I understand some basic principles from programming languages and code. When you are an experienced programmer, all of the below might sound plain simple to you, but this article is intended to help others like myself in the world of custom components for HA with no to only limited knowledge of Python or programming.

What is my device like that I try to integrate?

It is smart plug outlet which actually allows me to control the switch (on/off) of an outlet and allows me to measure power, energy consumed, see actual voltage or current. So it is more than just a switch, it is a sensor (measurement device) as well. It runs Busybox Linux and can be accessed via telnet (command line access) or a web interface (http). It is connected to a 2.4Ghz Wifi network.

Where did I start from?

I used the Edimax integration as an example for my code (from oct/2020). I took me some time but I found out that there are 2 major parts in the integration and there is 1 configuration part.

Integration:

  1. smartplug.py (under x)
  2. switch.py (under y)

Configuration:

  1. configuration.yaml (under z)

Deeper dive into the Edimax integration & configuration for Home Assistant

switch.py

This code will declare to HA that a switch entity is available but also that there is sensor information that can be read out. The code can be split up into the following parts:

  1. Import the smartplug library explained hereunder (see smartplug.py) and some other dependencies
  2. Define a new "domain": edimax
  3. Extend the platform schema (edimax) so that we can specify a host, username and password
  4. Set up the platform and add entities (as much as we define, and for this code: just 1)
  5. Define a new class to describe the smart plug entity (as added under step 4)
  6. Initialize the class (__init__) with some standard (required) parameters and some optional
  7. Define properties (values, states or readings) that are available or required for this entity
    1. Required (mandatory to define)
      1. unique_id (as the name states, something that makes this entity unique, not just the device or the component)

    2. Optional
      1. Your own properties that you want to expose or make available in HA
  8. Define some methods which are specific for the type of entity (switch) that we are adding, including what properties (values) to update (and how)

Since the architecture of HA has evolved since the creation of this entity, the original Edimax integration should be split up into a switch.py and sensor.py entity. The page on the device registry did shed some light and helped me understand how this should fit together. In short: a smart plug (device) like mine should have 2 or more entities (switch or sensor) which is seen as 1 component, when it has a config entry. Note that there will be a separate sensor entity for each measurement the smart plug is doing. (So a separate one for power, another one for voltage, current...)



So in my code, I started to split into switch.py and sensor.py but I'll explain that later.

smartplug.py

Here, the actual library is available that does all the work to get data from the smart plug. It will do in short the following:

  1. Import dependencies
  2. Create a class SmartPlug (which is then used in the switch.py code)
  3. Initialize and create a new instance that will connect to the smart plug on port 10000
  4. Methods for sending or formatting XML to the device to set/get info, power or state

And of course, it is from within switch.py that these methods are being called.

configuration.yaml

This is where you need to "enable" your integration by adding it to the configuration of HA. As long as you don't define anything here, there will never show up something in HA.

Example code:

- platform: edimax
  host: 192.168.133.119
  username: !secret switch_edimax_user
  password: !secret switch_edimax_password
  name: edimax1

How to install your custom library and define and enable devices in HA?

So now that I have a better understanding how the pieces of the puzzle fit together, I started adapting the existing scripts to work with my Maginon smart plugs. They should be read out over HTTP (not port 10000) and are also generating other/more simple output. After a lot of trial and error, reading logs and error messages and restarting my HA container 100s of times, I managed to get something working. :-)

Step 1: install the custom library that does not exist in the PyPi repository

1.1 Create a new folder & copy the files there

a

a

1.2 Test with a local file

a

1.3 Adapt the manifest file

a

1.4 Create a setup.py file

You must create a setup.py file as per example below in x where pymaginon is a subfolder of.
from setuptools import setup

setup(name='pymaginon',
      version='0.2.1',
      description='Interface with Maginon Smart Plugs',
      url='https://github.com/smartathome/pymaginon',
      author='Smart At Home',
      author_email='smartathome2022@gmail.com',
      license='MIT',
      install_requires=['requests>=2.0'],
      packages=['pymaginon'],
      zip_safe=False)


Checklist before you install:

  • You have created a directory for the library under /usr/local/lib/python3.9/site-packages/pymaginon
  • Optionally: you have tested your code with your library in a test script
  • You have adapted the manifest version
  • You have created a setup.py file

1.5 Install via python3 setup.py install

Next thing to do is to install for your python3 installation where you should have output like below. Then you should restart HA or the container. If all goes well (check the container logs if no errors are thrown), you should be good to go. 

Expected output from installation:
bash-5.1# python3 setup.py install
running install
running bdist_egg
running egg_info
creating pymaginon.egg-info
writing pymaginon.egg-info/PKG-INFO
writing dependency_links to pymaginon.egg-info/dependency_links.txt
writing requirements to pymaginon.egg-info/requires.txt
writing top-level names to pymaginon.egg-info/top_level.txt
writing manifest file 'pymaginon.egg-info/SOURCES.txt'
reading manifest file 'pymaginon.egg-info/SOURCES.txt'
adding license file 'LICENSE'
adding license file 'LICENSE.txt'
writing manifest file 'pymaginon.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
running build_py
creating build
creating build/lib
creating build/lib/pymaginon
copying pymaginon/__init__.py -> build/lib/pymaginon
copying pymaginon/smartplug.py -> build/lib/pymaginon
copying pymaginon/test.py -> build/lib/pymaginon
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/pymaginon
copying build/lib/pymaginon/__init__.py -> build/bdist.linux-x86_64/egg/pymaginon
copying build/lib/pymaginon/smartplug.py -> build/bdist.linux-x86_64/egg/pymaginon
copying build/lib/pymaginon/test.py -> build/bdist.linux-x86_64/egg/pymaginon
byte-compiling build/bdist.linux-x86_64/egg/pymaginon/__init__.py to __init__.cpython-39.pyc
byte-compiling build/bdist.linux-x86_64/egg/pymaginon/smartplug.py to smartplug.cpython-39.pyc
byte-compiling build/bdist.linux-x86_64/egg/pymaginon/test.py to test.cpython-39.pyc
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying pymaginon.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying pymaginon.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying pymaginon.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying pymaginon.egg-info/not-zip-safe -> build/bdist.linux-x86_64/egg/EGG-INFO
copying pymaginon.egg-info/requires.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying pymaginon.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
creating dist
creating 'dist/pymaginon-0.2.1-py3.9.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing pymaginon-0.2.1-py3.9.egg
creating /usr/local/lib/python3.9/site-packages/pymaginon-0.2.1-py3.9.egg
Extracting pymaginon-0.2.1-py3.9.egg to /usr/local/lib/python3.9/site-packages
Adding pymaginon 0.2.1 to easy-install.pth file

Installed /usr/local/lib/python3.9/site-packages/pymaginon-0.2.1-py3.9.egg
Processing dependencies for pymaginon==0.2.1
Searching for requests==2.25.1
Best match: requests 2.25.1
Adding requests 2.25.1 to easy-install.pth file

Using /usr/local/lib/python3.9/site-packages
Searching for urllib3==1.26.6
Best match: urllib3 1.26.6
urllib3 1.26.6 is already the active version in easy-install.pth

Using /usr/local/lib/python3.9/site-packages
Searching for chardet==4.0.0
Best match: chardet 4.0.0
chardet 4.0.0 is already the active version in easy-install.pth
Installing chardetect script to /usr/local/bin

Using /usr/local/lib/python3.9/site-packages
Searching for idna==2.10
Best match: idna 2.10
idna 2.10 is already the active version in easy-install.pth

Using /usr/local/lib/python3.9/site-packages
Searching for certifi==2021.5.30
Best match: certifi 2021.5.30
certifi 2021.5.30 is already the active version in easy-install.pth

Using /usr/local/lib/python3.9/site-packages
Finished processing dependencies for pymaginon==0.2.1

Step 2: add the component code under HA

2.1 x

sxx

Step 3: enable the component in the configuration of HA

xxx

And now restart HA or restart your container... and fingers crossed. :-)


General learnings and conclusion for myself

  • Altough I respect everyone involved in the community of HA and I'm grateful for all the available support and documentation, I still find some of the documentation very limited, not always clear and some real life examples (beyond a simple "hello world") are often missing.
  • There are some basic principles one needs to know (and respect) in order to be able to write a good component. Once you know these, it is more easy to build your own component.
  • The source code (and architecture) of Home Assistant is still in progress. Components written years ago, might make use of api or functionality that has changed or is no longer allowed to be used. So using them as an example or base for editing could bring some surprises.
  • Browse through the code tree and have a look at other components that are maintained and updated regularely. They are an excellent base for guidance and a source of inspiration (at least for me).
  • If you want to make use of your own written library as a base (in my case, I adapted pyedimax to pymaginon), you will have to install it manually since it does not exist in the PyPI repository. Until of course you decide to add such library there.
  • Use the logs that HA will throw out each time things don't work. They often will help you getting one step further. Also, make use of the LOGGER functionality so that custom error logs can be added when needed. When your code is more stable, look at how other components are making use of them, not to overwhelm your log files.
  • I am currently making use of and "old way" of adding my entities to HA. add_entities has been superseded by the async way of doing things. Something for the future. :-)

Feel free to comment or share useful other links to help others in understanding how custom components can be created more easily.

No comments:

Post a Comment