EDIMAX Smart Plug SP-1101W – Python API Update

In a previous post, I already tried to describe how the scheduling format of the EDIMAX Smart plug is to be encoded. Actually I think what was described there was confusing and not a 100% correct. Thus, here some additions and corrections. (Actually, all is pretty simple, since each minute of the day is represented by a bit. Every character in the schedule string represents four of the bits).

Also the code on github is updated.

General XML Format

When retrieving the schedules from the Plug, the following XML is returned:

<?xml version="1.0" encoding="UTF8"?>
<SMARTPLUG id="edimax">
<CMD id="get">
<SCHEDULE>
<Device.System.Power.Schedule.0 value="ON">F80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</Device.System.Power.Schedule.0>
<Device.System.Power.Schedule.1 value="ON">000000000000000FFFFFFFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</Device.System.Power.Schedule.1>
<Device.System.Power.Schedule.2 value="OFF">000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</Device.System.Power.Schedule.2>
<Device.System.Power.Schedule.3 value="OFF">000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFF000000000000000000000000000000000000000000000</Device.System.Power.Schedule.3>
<Device.System.Power.Schedule.4 value="OFF">000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</Device.System.Power.Schedule.4>
<Device.System.Power.Schedule.5 value="OFF">000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</Device.System.Power.Schedule.5>
<Device.System.Power.Schedule.6 value="OFF">000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</Device.System.Power.Schedule.6>
</SCHEDULE>
</CMD>
</SMARTPLUG>

For each day an entry like this is included:

<Device.System.Power.Schedule.X value="ON">...</Device.System.Power.Schedule.X>

X = 0 is Sunday, X = 1 is Monday, X = 6 is Saturday. The “value” attribute is “ON” if the schedule for this day is active or “OFF” if disabled. The data of the “Device.System.Power.Schedule.X” tag is 360 characters long. Each hour is represented by 15 characters, thus 360 characters = 24h. For further discussion we will focus on that 360 characters (let’s call this “packed schedule”.

Packed Schedule Format in Detail

Once again, we have 360 characters representing 24 hours. Thus 15 characters represent 1 hour. This again tells us, that one characters needs to represent 4 minutes to allow us to represent every minute of an hour.

The interesting question now is: how is the 60 minute information encoded (packed) into 15 characters? Well this is very simple. Each character represents four bits (or four minutes). If a bit is set, the minute is set. Then if one transforms each 4-bit value into its corresponding hex value, and if this hex values are joined together, the 15 character string for each hour could be build. By joining together all the 15 characters hour values, we get a whole days schedule.

The above could be best illustrated by example.

To schedule the minute from 00:00 – 00:01 the bits for the first hour would look like this:

1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

Which would be in hex:

8 0 0 0 0 0 0 0 0 0 0 0 0 0 0

And for 00:00 – 00:02:

1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

Again in hex:

C 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Now 00:00 – 00:03:

1110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

In hex:

E 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Then 00:00 – 00:04:

1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

In hex:

F 0 0 0 0 0 0 0 0 0 0 0 0 0 0

The same for the next for bytes with 00:00 – 00:05:

1111 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

In hex:

F 8 0 0 0 0 0 0 0 0 0 0 0 0 0

… And so forth

VoCore, Micropython, more Blinking

Lately I measured the maximum frequency I could get out of the VoCore by toggling a GPIO as fast as possible using various approaches. Today I wrote a simple Kernel module which allows me to set/clear a GPIO through IOCTL. Also I created a FFI binding to IOCTL for micropython and the usual test program for toggling the pin. And, no surprise, this is the fastest I was able to get so far from user space and micropython: 9kHz (remember, pure Kernel implementation was 14kHz).

Thus, the ranking now is:

Code Freq
Shell, sysfs 1.2kHz
Micropython, sysfs 1.6kHz
Python 2.7, sysfs 0.7kHz
C, sysfs 3.2kHz
Micropython, IOCLT, kmod 9.2kHz
Pure kmod 14kHz

The full source code could be found in this gist.

VoCore Maximum Blink Frequency

Today I did some testing on the maximum blink frequency I could get out of the VoCore using the “sysfs“ GPIO interface. I used four different candidates:

  • A shell script
  • A Python 2.7 program, basically the one described here (gist)
  • A Micropython program, more or less the same as the above, but using native libc file IO trough FFI as described here (gist)
  • And finally a C program

To summarize up the code used here the relevant snippets:

Shell Test Code

while true                           
do                                   
  echo 1 > /sys/class/gpio/gpio12/value
  echo 0 > /sys/class/gpio/gpio12/value
done

Python/Micropython Test Code

pin = DigitalIO.get_output(GPIO12)

while True:
  pin.set()
  pin.clear()

Full source:

C Test Code

#include <sys/types.h<
#include <sys/stat.h<
#include <fcntl.h<

#define SYSFS_ENTRY     "/sys/class/gpio/gpio22/value"

static int f;

void gpio_init()
{
        f = open(SYSFS_ENTRY, O_WRONLY | O_SYNC);
}

void gpio_term()
{
        close(f);
}

void gpio_set(void)
{
        write(f, "1", 1);
}

void gpio_clear(void)
{
        write(f, "0", 1);
}

int main()
{
        gpio_init();

        for(;;)
        {
                gpio_set();
                gpio_clear();
        }

        gpio_term();

        return 0;
}

Results

The results measured through OpenBench Logic Sniffer:

Code Freq
Shell 1.2kHz
Micropython 1.6kHz
Python 2.7 0.7kHz
C 13kHz

Addition: I also tried the same from within a Kernel module. By doing so, I was able to get about 14kHz.

The clear winner is C (note, that it is essential for the performance to NOT open/close the FD each time, but keep it open), followed by Micropython and the shell script. At the end is Python with very poor performance (which I could not explain up to now).

VoCore, Micropython and the SYSFS

As I wrote a while ago I was not able to access the Kernels SYSFS through the build-in “open/read/write” methods from micropython. I thought this might be because of the old version of micropython which comes from the OpenWrt binary repository, but even rebuilding OpenWrt did’t change anything.

Then I realized, there is FFI (foreigen function interface) support build into micropython. This allowed me to wrap the libc’s open/read/write operations directly into micropython. And finally, when using the wrapped libc methods, I am able to blink a LED from micropython on the VoCore. The source for this looks like so:

Simple Snap-In Case for Hacklace2 v2.0

I refined the previous case a little to have less height and rounded corners. Looks much lighter now. The new design could be found on thingiverse as usual.

Hacklace2 Snap-In Case v2.0

Hacklace2 Snap-In Case v2.0

Hacklace2 Snap-In Case v2.0

Hacklace2 Snap-In Case v2.0

Hacklace2 Snap-In Case v2.0

Hacklace2 Snap-In Case v2.0

VoCore Arrived

Today my VoCore modules arrived from China (they are amazingly tiny). So time for a first LED blink right? Ok, I wanted to get things working quickly so I decided to go with the pre-installed OpenWrt and Python + sysfs. Unfortunately there is not Python on the device, and also the right package sources are missing. However, this could be easily fixed.

VoCore Top

VoCore Top

VoCore Size

VoCore Size

VoCore Bottom

VoCore Bottom

VoCore compared to ESP8266

VoCore compared to ESP8266

1) Add additional package source to /etc/opkg.conf

src/gz cheese_packages http://downloads.openwrt.org/snapshots/trunk/ramips/packages/packages

2) Update package index

opkg update

3) Install Python

Unfortunately the Python package has a unresolved dependency (to libcrypt). Thus, we need to fore the install:

opkg install --force-depends python

Now we are able to use this simple script (since the sysfs GPIO support is already enabled on the VoCore):

VoCore 1st Blink

VoCore 1st Blink


Note on Micropython

I was very happy to see that micropython is available as a package from the OpenWrt repository. So this was my first try. Unfortunately, mircropython was not able to read/write the sysfs pseudo-files, and thus I had to revert to “real” Python.

Simple Snap-In Case for Hacklace2

Just created a simple snap-in case for the Hacklace2. Two versions are provided: One which houses a Hacklace2 with soldered on headers, one which houses a Hacklace2 without headers. The OpenSCAD sources and printable STL files could be found on thingiverse.

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

Hacklace2 Case

EDIMAX Smart Plug SP-1101W – Simple Python API

I puzzled the findings from my previous posts regarding the EDIMAX Smart plug (Basics, Scheduling) together and uploaded a simple Python API for the plug to my github. More details are available in the README of the project.

EDIMAX SP-W1101W

EDIMAX SP-W1101W

To give a quick impression of the API, here some examples (full code is here):

Set/Get plug state

# import plug API
from ediplug.smartplug import SmartPlug

# create plug object for plug with IP 172.16.100.75, login admin and password 1234
p = SmartPlug("172.16.100.75", ('admin', '1234'))

# change state of plug to ON
p.state = "ON"
print("Plug is now: ", p.state)

# change state of plug to OFF
p.state = "OFF"
print("Plug is now: ", p.state)

Set/Get scheduling

# import plug API
from ediplug.smartplug import SmartPlug

# create plug object for plug with IP 172.16.100.75, login admin and password 1234
p = SmartPlug("172.16.100.75", ('admin', '1234'))

# write schedule for one day to plug (Saturday, 11:15 - 11:45)
p.schedule = {'state': u'ON', 'sched': [[[11, 15], [11, 45]]], 'day': 6}

# query and print current schedule
print(p.schedule.__str__())

# write schedule for one week
p.schedule = [
        {'state': u'ON', 'sched': [[[1, 0], [1, 30]]], 'day': 0},
        {'state': u'ON', 'sched': [[[2, 0], [2, 30]]], 'day': 1},
        {'state': u'ON', 'sched': [[[3, 0], [3, 30]]], 'day': 2},
        {'state': u'ON', 'sched': [[[4, 0], [4, 30]]], 'day': 3},
        {'state': u'ON', 'sched': [[[5, 0], [5, 30]]], 'day': 4},
        {'state': u'ON', 'sched': [[[6, 0], [6, 30]]], 'day': 5},
        {'state': u'ON', 'sched': [[[7, 0], [7, 30]]], 'day': 6},
    ]

# query and print current schedule
print(p.schedule.__str__())

EDIMAX Smart Plug Switch SP-1101W – Scheduling

NOTE: Some of the content below is misleading! I tired to correct this in an other post. Thus, please make sure to read this post too!

As an response to my article on operating the EDIMAX Smart Plug with Python today I got an E-Mail from Jorge, asking if I could give some details on how to program the schedule of the EDIMAX Smart Plug. So here are some basics on how this could be done through HTTP/XML (sorry, no Python code yet basic Python API supporting scheduling).

01_http_post.sh

This is the shell script I use to post the example XML requests and print the results from the plug.

02_getsched.xml

Sample XML to query the current scheduling set on the plug. E.g. the following call:

./http_post.sh getsched.xml

Would return a result as shown in 02_getsched_response.xml. For each day we have an entry like:

 <Device.System.Power.Schedule.X value="ON">...</Device.System.Power.Schedule.X>

X = 0 is Sunday, X = 1 is Monday, X = 6 is Saturday. The “value” attribute is “ON” if the schedule for this day is active or “OFF” if disabled. The data of the “Device.System.Power.Schedule.X” tag is 360 bytes long. Each hour is represented by 15 bytes, thus 360 bytes = 24h.

Each byte in the hour could represent 1, 2, 3 or 4 minutes on an ending interval:

0 = 0 min
8 = 1 min
C = 2 min
D = 3 min
F = 4 min

And on an beginning interval:

0 = 0 min
7 = 1 min
3 = 2 min
1 = 3 min
F = 4 min

The sum of all hour bytes is 60. Thus, a full hour of scheduling could be represented by:

FFFFFFFFFFFFFFF

Half an hour would be:

FFFFFFFC0000000

This is because F = 4 min., C = 2 min. and thus: 4 + 4 + 4 + 4 + 4 + 4 + 4 + 2 = 30 min.

04_sched_sun_0100-0130.xml

So for example set a schedule for Sunday from 01:00 till 01:30, we could send the XML as shown in “04_sched_sun_0100-0130.xml”.

Propeller Tool-Chain Installer (GCC, SPIN, BST, ..) Updated

I updated my installer-script for the Parallax Propeller tool-chain (GCC, SPIN, BST and others) to make it work (agin) on Ubuntu 14.04. Main issue was that the current “makeinfo” seems not to be happy with some of the “.texi” files from “binutils” used by the Propeller GCC. Thus, I added a patch to the script to solve this problem. Also the open-source-spin compiler needed a small patch since it now uses “stricmp” which is not POSIX and thus not known …

Finally I dropped support for the “loader” hack which allowed compilation of the “propgcc” on non Intel platforms. This is not any more such an issue since cross-compiling of “propgcc” is now supported (see “propgcc” README).

NOTE: if you are on a 64bit platform an you are going to install BST Tools, you need to make sure to install the required i386 libraries since BST binaries are 32bit only. On Ubuntu 14.04 the following should do the trick:

sudo apt-get install libgtk2.0-0:i386