Showing posts with label maginon. Show all posts
Showing posts with label maginon. Show all posts

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.

Thursday, February 9, 2017

Looking inside a Wifi smartplug (Maginon/Edimax/STT)



I bought a smart plug from a local retailer which branded it as a Maginon SP1-E. These can also be found on the market as an Edimax SP-1101W. Originally, they are manufactured by ST&T.
For me, these plugs are very useful as they are a cheap solution for socket based electricity sub-metering and I can enable or disable the power remotely, which can be integrated into my domotics or smart home control center.

In a summary, the functions are:
  • Runs embedded Linux
  • Able to switch power
  • Able to access Wifi networks
  • Able to measure V, W, A and kWh
  • Can be controlled by a crappy smartphone app

By default, when plugged in, the plug will power-on, allow power consumption from it for 2-3 seconds, switch the breaker (and no longer allow power consumption) and then require that the user switches the breaker (through a command to the device) again to allow power consumption. This behavior can be seen as 'safe', yet it is a dirty way to trigger the breaker at startup. If the breaker would have been normal open by design, the 2-3 seconds power-on at the start, would not happen. Additionally, if you put this plug onto a freezer e.g., and a power disruption happens, your freezer will not be repowered after power restoration, until the user switches the breaker through a command to the device. Later on, we will tackle this short coming in another blog post.


Maginon has released source code for the plug under the GPLhttp://gpl.supra-elektronik.com/

The mechanics: outside

The front has 2 buttons and 2 LEDs. The main button (largest one) can be used to HW switch the breaker. When pressing the reset button for more than 8 seconds, the plug will go back to the factory settings.


The plug can be opened, using triangle bits.

The mechanics: inside

There are 2 main PCBs inside. The Embedded Linux board (Ralink RT5350F) which is low voltage and the breaker/metering board which runs at 230V.

Below are the images of the Embedded Linux board:


 Patch antenna on the left, wired to the Embedded Linux board


 Ralink Soc (System on Chip) board

Pins on the edges, to plug the board onto the breaker/metering board.

Here are some images of the breaker/metering board:

Look inside from the top
The breaker

Inside the Embedded Linux OS

Login into the plug can be done by telnet to the plug IP on port tcp/23. The default username/password is: admin/admin.

Checking the cpuinfo
# cat /proc/cpuinfo
system type             : Ralink SoC
processor               : 0
cpu model               : MIPS 24K V4.12
BogoMIPS                : 239.61
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 32
extra interrupt vector  : yes
hardware watchpoint     : yes
ASEs implemented        : mips16 dsp
VCED exceptions         : not available
VCEI exceptions         : not available
Linux version
# uname -a
Linux (none) 2.6.21 #324 Mon Aug 25 16:53:40 CST 2014 mips unknown
Checking the mount points
# mount   
rootfs on / type rootfs (rw)
proc on /proc type proc (rw)
none on /var type ramfs (rw)
none on /etc type ramfs (rw)
none on /tmp type ramfs (rw)
none on /media type ramfs (rw)
none on /dev/pts type devpts (rw)
Checking the filesystem
# df /
Filesystem           1k-blocks      Used Available Use% Mounted on
rootfs                       0         0         0   0% /
Checking the memory usage
# free   
              total         used         free       shared      buffers
  Mem:        29324         9984        19340            0            0
 Swap:            0            0            0
Total:        29324         9984        19340
The available tools within Busybox
# busybox
BusyBox v1.12.1 (2014-07-31 06:32:52 CEST) multi-call binary
Copyright (C) 1998-2008 Erik Andersen, Rob Landley, Denys Vlasenko
and others. Licensed under GPLv2.
See source distribution for full notice.

Usage: busybox [function] [arguments]...
   or: function [arguments]...

        BusyBox is a multi-call binary that combines many common Unix
        utilities into a single executable.  Most people will create a
        link to busybox for each function they wish to use and BusyBox
        will act like whatever it was invoked as!

Currently defined functions:
        [, [[, ash, brctl, cat, chmod, chpasswd, cp, crond, date, dd, df,
        du, echo, expr, free, grep, halt, ifconfig, init, init, insmod, kill,
        killall, login, ls, lsmod, mkdir, mknod, mount, ping, poweroff, printf,
        ps, pwd, reboot, rm, rmmod, route, sed, sh, sleep, syslogd, telnetd,
        test, tftp, top, touch, tr, udhcpc, udhcpd, umount, uname, uptime,
        vconfig, wc
The following binaries are available and not from Busybox
# cd /bin
# ls -l | grep -v "busybox"
-rwxr-xr-x    1 0        0          148244 iperf
-rwxr-xr-x    1 0        0           22804 ntpclient
-rwxr-xr-x    1 0        0            6208 erase
lrwxrwxrwx    1 0        0              11 nvram_set -> ralink_init
-rwxr-xr-x    1 0        0           36216 iwpriv
-rwxr-xr-x    1 0        0           36508 ralink_init
-rwxr-xr-x    1 0        0           79072 rt2860apd
-rwxr-xr-x    1 0        0            9032 gpio
-rwxr-xr-x    1 0        0            9928 flash
-rwxr-xr-x    1 0        0            6864 reg
-rwxr-xr-x    1 0        0          173476 miniupnpd
lrwxrwxrwx    1 0        0              11 nvram_get -> ralink_init
-rwxr-xr-x    1 0        0            9920 ated
-rwxr-xr-x    1 0        0            6536 nvram_daemon
-rwxr-xr-x    1 0        0           33992 mkfs.jffs2
-rwxr-xr-x    1 0        0          345156 goahead
-rwxrwxrwx    1 0        0          381421 RDTServer
lrwxrwxrwx    1 0        0               9 rtinicapd -> rt2860apd
-rwxrwxrwx    1 0        0           37798 setUID
-rwxr-xr-x    1 0        0          170687 GpioForCrond
-rwxrwxrwx    1 0        0           37901 writeflash
-rwxr-xr-x    1 0        0           88976 iptables
-rwxr-xr-x    1 0        0            5264 mii_mgr
-rwxr-xr-x    1 0        0           83848 GetInfo
-rwxrwxrwx    1 0        0           48789 checksum
-rwxr-xr-x    1 0        0           39975 UartForCrond
-rwxr-xr-x    1 0        0           12744 mtd_write
-rwxr-xr-x    1 0        0           74124 lld2d
-rwxr-xr-x    1 0        0           16236 switch
-rwxrwxrwx    1 0        0           37798 getUID
-rwxr-xr-x    1 0        0            6164 eraseall
The below command will enable the power consumption on the plug.
# /bin/GpioForCrond 1
gpio_set_dir: gpio=2, dir=0
gpio_set_dir: gpio=1, dir=1
InitGpio() success, fd = 3
Get GPIO1 = [01]
The below command will disable the power consumption on the plug.
# /bin/GpioForCrond 0
gpio_set_dir: gpio=2, dir=0
gpio_set_dir: gpio=1, dir=1
InitGpio() success, fd = 3
Get GPIO1 = [00]
Several interfaces are available for connecting. The apcli0 is the WAN interface where the plug acts as a client for. In this case, this is connected to the home Wifi network in the network range 192.168.0.0/24. The br0 is the "server" interface where the plug can give IP leases to other clients (e.g. your laptop), which are accessing the plug's Wifi network. This Wifi network has a SSID which is always named as "Smartplug_XXXXXX" where XXXXXX is the last 6 chars of the plug's br0 MAC address, in this case 11A408.
# ifconfig
apcli0    Link encap:Ethernet  HWaddr 00:08:ED:11:A4:09
          inet addr:192.168.0.235  Bcast:192.168.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

br0       Link encap:Ethernet  HWaddr 00:08:ED:11:A4:08
          inet addr:10.10.10.254  Bcast:10.10.10.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:36 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:1512 (1.4 KiB)

eth2      Link encap:Ethernet  HWaddr 00:0C:43:30:50:77
          UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5345 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:548821 (535.9 KiB)
          Interrupt:3

eth2.1    Link encap:Ethernet  HWaddr 00:0C:43:30:50:77
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth2.2    Link encap:Ethernet  HWaddr 00:0C:43:30:50:66
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:1588 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1588 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:191850 (187.3 KiB)  TX bytes:191850 (187.3 KiB)

ra0       Link encap:Ethernet  HWaddr 00:08:ED:11:A4:08
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:70094428 errors:203 dropped:0 overruns:0 frame:0
          TX packets:923762 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:3887818972 (3.6 GiB)  TX bytes:88933398 (84.8 MiB)
          Interrupt:4