Energenie LAN Power Management System - 4 IP Controlled Mains Sockets

Hi

I was just wondering if anyone else has bought one of these and has got it working with Vera? Assuming the http requests used are pretty straight forward it’s my hope that I can create some simply lua code to turn them off and on.

http://www.automatedhome.co.uk/reviews/energenie-lan-power-management-system-4-ip-controlled-mains-sockets.html

I should get mine in a few days so any thoughts/feedback greatly appreciated…

They provide an SDK (Service) which appears to have a description of the protocol. It looks a mite messy and lacks information such as whether it uses TCP or UDP and what port it uses. You might be able to reverse engineer the missing parts of the protocol with a packet sniffer.

It appears that there’s no way to ask the device to inform you out-of-band when the state of one of its switches changes, so you’d be reduced to polling every few seconds to get the current state.

Hi @futzle

Thanks for responding. I can see port 5000 is mentioned in the user manual (http://www.energenie.com/Repository/6668/EG-PMS-LAN_manual---28781382-f189-4f75-9b41-ad98228d5742.pdf) so hopefully that’s a start.

Plus with a built in webserver it’s my hope that’ I’ll be able to see something in the URL construction (assuming it’s visible)

Worse case I do have wireshark on my PC (not that I know how to use it :wink: ) so I can try and monitor traffic and see if I extract some thing of use…

Also found these which might be of use, the last one especially (if only I could fully understand it)!

Well the PMS-LAN multi socket arrived today and it looks a nice (solid) piece of kit…

The web interface is very simple and it looks like it uses java script to turn the 4 managed sockets on or off, simply viewing source of one of the pages I extracted the following to see if I can work out what I would need to call via a Luup script, although I will admit I was hopeing it was going to be easier than this…

</div></div><div id="content"><script>function SetSch(){document.location.href = "set.html?sn=" + sn;}function SiwtchOnOff(){f = document.forms.fchst;f["ctl" + sn].value = Math.abs(1 - sst);f.submit();}</script><div id="title"><h1>Socket <span id="snT"></span><span class="snm" id="csnm"></span></h1><div><h2><a href="javascript: SetSch()">Set schedule</a></h2></div><div class="cleaner"><!-- --></div></div><form action="./" method="post" onsubmit="return SiwtchOnOff();" id="fchst"><span class="off" id="sState">OFF</span>&nbsp;&nbsp;<input type="submit" id="sbtn" value="Switch off" /><br/><br/>

The links in my previous posts show code people have used created controls via a bash script and also php, so it’s my hope that with the help of the expertise on this forum i could create a simple script or maybe a very basic plugin for it

“SiwtchOnOff()”. Oh boy. Good luck. You’ll need it.

Putting the bad spelling to one side, am I flogging that proverbial dead horse ?

It’s hard to know yet. Despite their so-called “open” API and SDK, these kinds of devices don’t always match their documentation and are usually held together with the programmatic equivalent of duct tape. The spelling mistake is a good indication of the quality of the product’s firmware.

If you want to use this thing as an excuse to get your hands dirty, learn how to reverse-engineer protocols and sniff packets, learn JavaScript and sockets… I can think of worse projects to start with. You’ll certainly come out of it a better programmer and more knowledgeable about low-level programming. But you’re going to get frustrated with it when you inevitably hit one of their stupid design decisions, and you’re going to find that you spend more time writing learning-code and test programs than actual plugin. If I were in your position I’d set myself a goal of controlling the switch from a PC without using their SDK. At least that way you’ll have a proper IDE with debugging tools. Once you’ve got that working, then we can talk about porting it to a Vera plugin.

Nothing ventured, nothing gained, although I’ve moved ove to iOS now so my (very) old PC might not be up to much. I’ll move this to the programming section now as I’ll be talking about code - and who knows someone might be sitting on an answer already…

Hi parkerc. I just wanted to let you know that I also bought one of these Energenie units. I’m going to try and figure out if it is possible to operate with simple http calls via a web brower, in the hope that this will provide someone with information that could be used to make a simple plugin.
If you have already made some progress on this investigation, please do let me know. Thanks.

Hi @jtmoore

Welcome to my quest to integrate this with Vera :slight_smile:

Unfortunately I’ve been unstable to do much more I wanted to use wire shark to see if I could capture what was being sent/received by the power strip but the network card on my old PC refuses to work and then before I could do anything to fix iot replace it, the day job got in the way.

I think with the help of others on this forum (see link below) we’ve explored a lot of http requests possibilities but a fresh pair of eyes will be great.

It’s a nice product and would be a good integration for Vera

I’ve also tried emailing Energenie direct a few times for help but i’ve so far been ignored :frowning:

If OK with you it would be best to share your findings via my plugin post, as you can also see what I’ve already tried…

http://forum.micasaverde.com/index.php/topic,17008.0.html

Hi all,

I got this device too. Using the protocol description I successfully wrote a small perl script that can turn on and off the plugs using the protocol on port 5000 (not the webserver itself). If your’e interested, it looks like this (should not be difficult to implement it in any other language), $pass and $ip must be filled in of course:

use strict;
use IO::Socket::INET;

my $pass = "...";
my $ip = "...";

sub response($$)
{
  my @d = map (ord, split //,shift);
  my @k = map (ord, split //,shift);
  my $v1 = (($d[0]^$k[2]) * $k[0])  ^  ($k[6] | ($k[4]<<8))  ^  $d[2];
  my $v2 = (($d[1]^$k[3]) * $k[1])  ^  ($k[7] | ($k[5]<<8))  ^  $d[3];
  return join "",map(chr, ($v1%256,$v1>>8,$v2%256,$v2>>8));
}

sub encrypt($$$)
{
  my @d = map (ord, split //,shift);
  my @k = map (ord, split //,shift);
  my @t = map (ord, split //,shift);
  my @r = (0,0,0,0);
  my $x;
  for (my $i=0; $i<4; $i++)
  {
    $x = $d[3-$i];
    $x ^=  $t[2];
    $x += $t[3];
    $x ^= $k[0];
    $x += $k[1];
    $r[$i] = 0xff & $x;
  }
  return join "",map(chr, @r);
}

sub decrypt($$$)
{
  my @d = map (ord, split //,shift);
  my @k = map (ord, split //,shift);
  my @t = map (ord, split //,shift);
  my @r = (0,0,0,0);
  my $x;
  for (my $i=0; $i<4; $i++)
  {
    $x = $d[$i];
    $x -= $k[1];
    $x ^= $k[0];
    $x -= $t[3];    
    $x ^= $t[2];
    $r[3-$i] = 0xff & $x;   
  }
  return join "",map(chr, @r);
}

sub setPlug($$$)
{
  my $ip = shift;
  my $idx = shift;
  return 0 if $idx<0 or $idx>3; # plugs 0-3
  my $flg = shift;
  return 0 if $flg<0 or $flg>2; # 0: turn off, 1: turn on, 2: toggle

  my $done = 0;
  while (!$done)
  {
	  my $s;
	  eval
	  {
		local $SIG{ALRM} = sub { die 'Timed Out'; };
		alarm 10; 
		my ($t,$d);
		$s =  new IO::Socket::INET (
		  PeerHost => $ip,
		  PeerPort => '5000',
		  Proto => 'tcp',
		) or return 0;

		$s->send("\x11");
		$s->recv($t,4);

		$s->send(response($t,$pass));
		$s->recv($d,4);
		$d = decrypt($d,$pass,$t);
  
		my $ctrl = "\x04\x04\x04\x04";  # default is: no action on any plug
		if ($flg==0) # turn off, unless it already is off
		{
		  substr($ctrl,$idx,1) = "\x02" unless substr($d,$idx,1) eq "\x82";
		}
		if ($flg==1) # turn on, unless it alredy is on
		{
		  substr($ctrl,$idx,1) = "\x01" unless substr($d,$idx,1) eq "\x41";
		}
		if ($flg==2) # toggle
		{
		  substr($ctrl,$idx,1) = chr((ord(substr($d,$idx,1))&1) + 1);
		}

		$s->send(encrypt($ctrl,$pass,$t));
		$s->recv($d,4);
		$d = decrypt($d,$pass,$t);
		$done=1;

		$s->send("\x01\x02\x03\x04");
		alarm 0;
	  };
	  alarm 0;
      $s->close() if defined $s;
  }
  return 1;
}


setPlug($ip,0,0); # turn off plug 1
setPlug($ip,0,1); # turn on plug 1
setPlug($ip,0,2); # toggle plug 1

The while/eval construct is just there to repeat the TCP connection in case of a problem. You might want to increase/decrase the “alarm 10” value - in this setup, the connection is re-tries after 10 seconds if it does not succeeds (timeout).

Hope it helps,

Andy

This is great Andy (Klaymen).

I was just working my way through the protocol myself trying to get something going in Python (I’m a novice, but thought that controlling the socket with a Raspberry Pi would make a nice little programming project).

Your code will be very useful.

Just one question - I can see how the first 4 lines of the ‘response’ function match the protocol, but don’t get why the mod 256 / bitwise right shift is needed. Is it mentioned in the protocol?

hi,

I would like to thank Klaymen! I was looking for some code to use with the energenie… I’v translated, as a test, with c# and xaml.
The code is not cleaned as I did it quickly… but I hope it will help peolpe. At least it’s working!

Change change this variable:
string pass = “”;(use a password with more than 8 characters)
string host =“”;
string port =“”;

I’ve use 3 ToggleButtons and 3 ToggleSwitchs to test.

Pierre

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Networking.Sockets;
using Windows.Networking;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using System.Text;
using Windows.Storage;

// The Blank Page item template is documented at C#, VB, and C++ project templates for Store apps - Windows app development | Microsoft Learn

namespace TestMultiPrise
{
///


/// An empty page that can be used on its own or navigated to within a Frame.
///

public sealed partial class MainPage : Page
{
StreamSocket socket;
string t;
string d;
string pass = “”;
string host =“”;
string port =“”;
bool start = true;

    public MainPage()
    {
        this.InitializeComponent();
        getState();

    }


    private string response(string s1, string s2)
    {

        byte[] a_s1 = Encoding.GetEncoding("ISO_8859-1").GetBytes(s1);

        byte[] a_s2 = Encoding.GetEncoding("ISO_8859-1").GetBytes(s2);

        var v_s1 = ((s1[0]^s2[2]) * s2[0])  ^  (s2[6] | (s2[4]<<8))  ^  s1[2];
        var v_s2 = ((s1[1] ^ s2[3]) * s2[1]) ^ (s2[7] | (s2[5] << 8)) ^ s1[3];

        var r1 = char.ConvertFromUtf32( v_s1%256);
        var r2 = char.ConvertFromUtf32(v_s1>>8);
        var r3 = char.ConvertFromUtf32(v_s2 %256);
        var r4 = char.ConvertFromUtf32(v_s2 >>8);

        var result = r1 + r2 + r3 + r4;

        return result;
    }


    private string encrypt(string s1, string s2, string s3)
    {

        byte[] a_s1 = Encoding.GetEncoding("ISO_8859-1").GetBytes(s1);// Encoding.UTF8.GetBytes(s1);

        byte[] a_s2 = Encoding.GetEncoding("ISO_8859-1").GetBytes(s2);//Encoding.UTF8.GetBytes(s2);

        byte[] a_s3 = Encoding.GetEncoding("ISO_8859-1").GetBytes(s3);//Encoding.UTF8.GetBytes(s3);

        int[] a_s4 = new int[4];

        for(int i=0;i<4;i++)
        {
            int x = a_s1[3 - i];
            x ^= a_s3[2];
            x += a_s3[3];
            x ^= a_s2[0];
            x += a_s2[1];
            a_s4[i] = (255 & x);

        }

        var result = char.ConvertFromUtf32(a_s4[0]) +char.ConvertFromUtf32( a_s4[1]) +char.ConvertFromUtf32( a_s4[2]) +char.ConvertFromUtf32( a_s4[3]);

        return result;
    }


    private string decrypt(string s1, string s2, string s3)
    {
        byte[] a_s1 =  Encoding.GetEncoding("ISO_8859-1").GetBytes(s1);// Encoding.UTF8.GetBytes(s1);

        byte[] a_s2 = Encoding.GetEncoding("ISO_8859-1").GetBytes(s2);//Encoding.UTF8.GetBytes(s2);

        byte[] a_s3 = Encoding.GetEncoding("ISO_8859-1").GetBytes(s3);//Encoding.UTF8.GetBytes(s3);

        int[] a_s4 = new int[4];

        for (int i = 0; i < 4; i++)
        {
            int x = a_s1[i];
            x -= a_s2[1];
            x ^= a_s2[0];
            x -= a_s3[3];
            x ^= a_s3[2];
            a_s4[3-i] = (255 & x);

        }

        var result = char.ConvertFromUtf32(a_s4[0]) + char.ConvertFromUtf32(a_s4[1]) + char.ConvertFromUtf32(a_s4[2]) + char.ConvertFromUtf32(a_s4[3]);

        return result;
    }

    public async Task getState()
    {
        HostName hostName;

        using (socket = new StreamSocket())
        {
            hostName = new HostName(host);

            // Set NoDelay to false so that the Nagle algorithm is not disabled
            socket.Control.NoDelay = false;

            try
            {
                // Connect to the server
                await socket.ConnectAsync(hostName, port);
                // Send the message
                await this.send("\x11");
                // Read response
                t = await this.read();

                string resp = response(t, pass);
                await this.send(resp);
                d = await this.read();
                d = decrypt(d, pass, t);

                for (int idx = 1; idx <= 4; idx++)
                {
                    if (d.Substring(idx - 1, 1) == "\x82")
                    {
                        ToggleSwitch tg = (ToggleSwitch)this.FindName("toggle" + idx.ToString());
                        if(tg!=null)
                        {
                            tg.IsOn = false;
                        }
                    }
                    else if (d.Substring(idx - 1, 1) == "\x41")
                    {
                        ToggleSwitch tg = (ToggleSwitch)this.FindName("toggle" + idx.ToString());
                        if (tg != null)
                        {
                            tg.IsOn = true;
                        }
                    }
                }                    

                await this.send("\x01\x02\x03\x04");
                start = false;
            }
            catch (Exception exception)
            {
                switch (SocketError.GetStatus(exception.HResult))
                {
                    case SocketErrorStatus.HostNotFound:
                        // Handle HostNotFound Error
                        throw;
                    default:
                        // If this is an unknown status it means that the error is fatal and retry will likely fail.
                        throw;
                }
            }
        }
    }

    public async Task connect( int flag, int idx)
    {
        HostName hostName;

        using (socket = new StreamSocket())
        {
            hostName = new HostName(host);

            // Set NoDelay to false so that the Nagle algorithm is not disabled
            socket.Control.NoDelay = false;

            try
            {
                start = true;
                // Connect to the server
                await socket.ConnectAsync(hostName, port);
                // Send the message
                await this.send("\x11");
                // Read response
                t = await this.read();

                string resp = response(t, pass);
                await this.send(resp);
                d = await this.read();
                d = decrypt(d, pass, t);


                string ctrl = "\x04\x04\x04\x04\x04";  // default is: no action on any plug

                if(flag == 0)
                {
                    if(d.Substring(idx-1,1)!="\x82")
                    {
                        ctrl = ctrl.Substring(0, (idx - 1) * 1) + "\x02" + ctrl.Substring(idx, 4 - idx);
                        ToggleSwitch tg = (ToggleSwitch)this.FindName("toggle" + idx.ToString());

                        if (tg != null)
                        {
                            tg.IsOn = false;
                        }
                    }
                }
                else if(flag==1)
                {
                    if (d.Substring(idx - 1, 1) != "\x41")
                    {
                        ctrl = ctrl.Substring(0, (idx - 1) * 1) + "\x01" + ctrl.Substring(idx, 4 - idx);

                        ToggleSwitch tg = (ToggleSwitch)this.FindName("toggle" + idx.ToString());
                        if (tg != null)
                        {
                            tg.IsOn = true;
                        }
                    }
                }
                else if(flag==2)
                {
                    if (d.Substring(idx - 1, 1) != "\x82")
                    {
                        ctrl = ctrl.Substring(0, (idx - 1) * 1) + "\x02" + ctrl.Substring(idx, 4 - idx);

                        ToggleSwitch tg = (ToggleSwitch)this.FindName("toggle" + idx.ToString());
                        if (tg != null)
                        {
                            tg.IsOn = false;
                        }
                    }
                    else if (d.Substring(idx - 1, 1) != "\x41")
                    {
                        ctrl = ctrl.Substring(0, (idx - 1) * 1) + "\x01" + ctrl.Substring(idx, 4 - idx);

                        ToggleSwitch tg = (ToggleSwitch)this.FindName("toggle" + idx.ToString());
                        if (tg != null)
                        {
                            tg.IsOn = true;
                        }
                    }
                }

                resp = encrypt(ctrl, pass, t);
                await this.send(resp);
                d = await this.read();
                d = decrypt(d, pass, t);
                await this.send("\x01\x02\x03\x04");
                start = false;

            }
            catch (Exception exception)
            {
                switch (SocketError.GetStatus(exception.HResult))
                {
                    case SocketErrorStatus.HostNotFound:
                        // Handle HostNotFound Error
                        throw;
                    default:
                        // If this is an unknown status it means that the error is fatal and retry will likely fail.
                        throw;
                }
            }
        }
    }

    /// <summary>
    /// SEND DATA
    /// </summary>
    /// <param name="message">Message to server</param>
    /// <returns>void</returns>
    public async Task send(string message)
    {
        DataWriter writer;

        // Create the data writer object backed by the in-memory stream. 
        using (writer = new DataWriter(socket.OutputStream))
        {
            // Set the Unicode character encoding for the output stream
            writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
            // Specify the byte order of a stream.
            writer.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;

            // Gets the size of UTF-8 string.
            writer.MeasureString(message);
            // Write a string value to the output stream.

            //writer.WriteString(message);
            var buffer = Encoding.GetEncoding("ISO_8859-1").GetBytes(message.ToString());
            writer.WriteBuffer(buffer.AsBuffer());


            // Send the contents of the writer to the backing stream.
            try
            {
                await writer.StoreAsync();
            }
            catch (Exception exception)
            {
                switch (SocketError.GetStatus(exception.HResult))
                {
                    case SocketErrorStatus.HostNotFound:
                        // Handle HostNotFound Error
                        throw;
                    default:
                        // If this is an unknown status it means that the error is fatal and retry will likely fail.
                        throw;
                }
            }

            await writer.FlushAsync();
            // In order to prolong the lifetime of the stream, detach it from the DataWriter
            writer.DetachStream();
        }
    }

    /// <summary>
    /// READ RESPONSE
    /// </summary>
    /// <returns>Response from server</returns>
    public async Task<String> read()
    {
        DataReader reader;
        StringBuilder strBuilder;

        using (reader = new DataReader(socket.InputStream))
        {
            strBuilder = new StringBuilder();

            // Set the DataReader to only wait for available data (so that we don't have to know the data size)
            reader.InputStreamOptions = Windows.Storage.Streams.InputStreamOptions.Partial;
            //The encoding and byte order need to match the settings of the writer we previously used.
            reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
            reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
            
            // Send the contents of the writer to the backing stream. 
            // Get the size of the buffer that has not been read.

            await reader.LoadAsync(256);

            Byte[] c = reader.ReadBuffer(4).ToArray();

            //char[] test = Encoding.GetEncoding("ISO_8859-1").GetChars(c);
            //StringBuilder strBuilder_test = new StringBuilder();

            reader.DetachStream();

            for (int i = 0; i < 4;i++ )
            {
                strBuilder.Append(char.ConvertFromUtf32(c[i]));
                //strBuilder_test.Append(test[i]);
            }

                return strBuilder.ToString();
                //return strBuilder_test.ToString();
        }
    }



    private async void prise_Tapped(object sender, TappedRoutedEventArgs e)
    {
        ToggleButton b = (ToggleButton)sender;
        int i_button = int.Parse(b.Content.ToString());
        bool on = (bool)b.IsChecked;

        await connect( 2, i_button);
    }


    private async void toggle_Toggled(object sender, RoutedEventArgs e)
    {
        if(!start)
        {
            ToggleSwitch b = (ToggleSwitch)sender;
            int i_button = int.Parse(b.Header.ToString());
            bool on = (bool)b.IsOn;
            int flag = 1;

            if (!on) flag = 0;

            await connect(flag, i_button);
        }

    }
}

}

@kevloc
were you able to write a program for the energenie lan device in Python. If yes, then could you share it with me. thanks

OK - To try and bring a few related threads together - and if you want to see my continuing efforts to try and control this bank of power sockets via Vera - please go here…

http://forum.micasaverde.com/index.php/topic,17008.0.html