Sunday, November 8, 2015

High speed "cron" plugins more

I am going to present three more plugins for the previous "cron_native" program.
1. Turn off Hue light bulb as soon as possible.
2. Turn off Hue light bulb an hour after it is turned on.
3. Turn off Hue light bulb 10 minutes after a TV is turned on.

Controlling Hue light bulb is already explained before; [link to more info]. And I posted how to detect whether TV is turned on/off already; [link to more info]. This article is to show the actual working example of the plugins for "cron_native".

I have six files for this:
pi@fileserver 01:37:55 plugins$ ls
Makefile     plugin.hello.cpp        plugin.hue_outdoor.cpp  stdafx.h
my_curl.hpp  plugin.hue_bedroom.cpp  plugin.hue_tv_room.cpp
pi@fileserver 01:37:57 plugins$
The file, "plugin.hello.cpp", is not a part of this article but for the previous one.

The first program to show is "plugin.hue_bedroom.cpp", which turns off the light as soon as possible.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// plugin.hue_bedroom.cpp written by JaeHyukKwak
#include "stdafx.h"
#include "my_curl.hpp"
static string MAC_HUE = "00:17:88:09:ef:18";
static uint32_t s_last_checked;

extern "C" void dl_init( uint32_t ) { s_last_checked = 0; }
extern "C" void dl_shutdown( uint32_t ) {}
extern "C" void dl_main( uint32_t cur_time )
{
    if ( cur_time - s_last_checked <= 10 ) return; // every 10 seconds
    s_last_checked = cur_time;

    ifstream fin( "/proc/net/arp" );
    string ip_hue = "192.168.0.50";
    for ( string line; !fin.eof(); )
    {   // ex: 192.168.0.50  0x1  0x2  00:17:88:09:ef:18  *  eth0
        getline( fin, line );
        string ip = line.substr( 0, line.find( ' ' ) );
        if ( line.find( MAC_HUE ) != string::npos ) ip_hue = ip;
    }
    string URL = string( "http://" ) + ip_hue;
    URL += "/api/newdeveloper/lights/1/state";

    my_curl_t curl;
    curl.SetOpt_URL( URL );
    curl.SetOpt_READDATA( "{\"on\":false}" );
    curl.RequestPut();
}
This program assumes the light index is "1" and the user account for the Hue Bridge is "newdeveloper". The IP address of my Hue Bridge happens to be "192.168.0.50" for now but it is dynamically allocated from the router so it may change anytime. The actual IP address is searched from ARP cache table. I once noticed that there was a case where Hue Bridge doesn't appear on the ARP cache table. I am not sure how the network protocol works but it seems that when the device is not active for long period of time, other devices may not award of the existence of the device in the network. That's why it starts with the fix IP address and update it with ARP cache table.

Since the goal of the program is to turn off the light as soon as possible, it doesn't need to check to see if the light is turned on or not. It will work either way.

I wanted to have the program running every 10 seconds; not every seconds. Each HTTP request is not much cost in terms of CPU and network bandwidth. I may add up and become big when I have many other plugins running together.

The second program, "plugin.hue_outdoor.cpp", is to turn off the hue light only if it is turned on for an hour. As the file name implies, this is to turn off the out door light automatically in case my wife forgot to turn it off.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// plugin.hue_outdoor.cpp written by JaeHyukKwak
#include "stdafx.h"
#include "my_curl.hpp"
static string MAC_HUE = "00:17:88:09:ef:18";
static uint32_t s_last_checked, s_last_inactive;

extern "C" void dl_init( uint32_t cur_time )
{
    s_last_checked = 0;
    s_last_inactive = cur_time;
}
extern "C" void dl_shutdown( uint32_t ) {}
extern "C" void dl_main( uint32_t cur_time )
{
    if ( cur_time - s_last_checked <= 60 ) return; // every minute
    s_last_checked = cur_time;

    ifstream fin( "/proc/net/arp" );
    string ip_hue = "192.168.0.50";
    for ( string line; !fin.eof(); )
    {   // ex: 192.168.0.50  0x1  0x2  00:17:88:09:ef:18  *  eth0
        getline( fin, line );
        string ip = line.substr( 0, line.find( ' ' ) );
        if ( line.find( MAC_HUE ) != string::npos ) ip_hue = ip;
    }
    string URL = string( "http://" ) + ip_hue;
    URL += "/api/newdeveloper/lights/4";

    my_curl_t curl;
    curl.SetOpt_URL( URL );
    string reply = curl.RequestGet();

    bool turned_on = [&]
    {
        bool inaccessible = ( reply.find( "\"on\":" ) == string::npos );
        if ( inaccessible ) return false;
        return ( reply.find( "\"on\":true" ) != string::npos );
    }();

    if ( !turned_on ) s_last_inactive = cur_time;
    else if ( cur_time - s_last_inactive >= 60 * 60 ) // 60min
    {
        syslog( LOG_NOTICE, "Light#4 is on for 60min and turning off" );
        curl.SetOpt_URL( URL + "/state" );
        curl.SetOpt_READDATA( "{\"on\":false}" );
        curl.RequestPut();
        s_last_inactive = cur_time;
    } else {} // turned on but not waited long enough
}
The program runs every minute and see if the light#4 is turned on or not. If it is turned on, the another timer starts to see how long the light was turned on. When the light is turned on more than an hour, it leaves a syslog message and turns the light off.

The last program, "plugin.hue_tv_room.cpp", is to turn off the light when TV is turned on. But it turns off only once, meaning TV is turned off, the light will be turned off but when I turn on the light manually while the TV is turned on, the light wouldn't be turned off again. I could also have it turn on the light back when TV is turned off, but I didn't find it useful because when I turn off the TV, most likely I was leaving the room.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// plugin.hue_tv_room.cpp written by JaeHyukKwak
#include "stdafx.h"
#include "my_curl.hpp"
static const char *MAC_TV = "f4:7b:5e:41:91:00";
static const char *MAC_HUE = "00:17:88:09:ef:18";
static uint32_t s_last_inactive, s_last_checked;
static bool s_light_once; // turn off light only once

extern "C" void dl_init( uint32_t cur_time )
{
    s_last_checked = 0;
    s_last_inactive = cur_time;
    s_light_once = true;
}
extern "C" void dl_shutdown( uint32_t ) {}
extern "C" void dl_main( uint32_t cur_time )
{
    if ( cur_time - s_last_checked <= 60 ) return; // every 60 seconds
    s_last_checked = cur_time;

    my_curl_t curl;
    string ip_hue = "192.168.0.50"; // in case not found from ARP
    bool is_tv_on = [&]
    {
        ifstream fin( "/proc/net/arp" );
        string ip_tv;
        for ( string line; !fin.eof(); )
        {   // ex: 192.168.0.53  0x1  0x0  f4:7b:5e:41:91:00  *  eth0
            getline( fin, line );
            string ip = line.substr( 0, line.find( ' ' ) );
            if ( line.find( MAC_TV ) != string::npos ) ip_tv = ip;
            else if ( line.find( MAC_HUE ) != string::npos ) ip_hue = ip;
        }
        if ( ip_tv.empty() ) return false; // TV IP not found.

        string msg = "Server returned nothing (no headers, no data)";
        curl.SetOpt_URL( string( "http://" ) + ip_tv + ":55000" );
        try { curl.RequestGet(); } // expected to throw always
        catch ( runtime_error &e ) { return ( msg == e.what() + 28 ); }
        catch ( ... ) { return false; }
        return false;
    }();
    if ( !is_tv_on )
    {   // when TV is off
        s_last_inactive = cur_time;
        s_light_once = true;
    } else if ( cur_time - s_last_inactive >= 60 ) // wait 1 minute
    {   // when TV is on and waited enough
        if ( !s_light_once ) return;
        s_light_once = false;
        syslog( LOG_NOTICE, "TV is turned on and turning off light#2" );

        const string URL_set = "/api/newdeveloper/lights/2/state";
        curl.SetOpt_URL( string( "http://" ) + ip_hue + URL_set );
        curl.SetOpt_READDATA( "{\"on\":false}" );
        curl.RequestPut();
    } else {} // when TV is on but not waited enough
}
My Samsung TV doesn't actually respond with HTTP protocol. It seems to have its own kind of protocol. So technically I am supposed to open a Socket connection to the port and see it the port is accessible or not. But that would require more low level Socket programming code, which isn't too hard with Boost library; [link to more detail]. I found that I can work around it with cURL as it is shown in the source code.

Makefile looks like this:
# Makefile written by Jae Hyuk Kwak
CC=g++-4.7
CFLAGS=-Wall -g -std=c++11

all: plugin.hello.so plugin.hue_outdoor.so plugin.hue_tv_room.so

plugin.hello.so: plugin.hello.cpp stdafx.h.gch
    -$(CC) $(CFLAGS) -shared -fPIC -o plugin.hello.so plugin.hello.cpp

plugin.hue_outdoor.so: plugin.hue_outdoor.cpp my_curl.hpp stdafx.h.gch
    -$(CC) $(CFLAGS) -shared -fPIC -lcurl -o plugin.hue_outdoor.so plugin.hue_outdoor.cpp

plugin.hue_tv_room.so: plugin.hue_tv_room.cpp my_curl.hpp stdafx.h.gch
    -$(CC) $(CFLAGS) -shared -fPIC -lcurl -o plugin.hue_tv_room.so plugin.hue_tv_room.cpp

plugin.hue_bedroom.so: plugin.hue_bedroom.cpp my_curl.hpp stdafx.h.gch
    -$(CC) $(CFLAGS) -shared -fPIC -lcurl -o plugin.hue_bedroom.so plugin.hue_bedroom.cpp

stdafx.h.gch : stdafx.h
    $(CC) $(CFLAGS) -c stdafx.h

clean:
    rm stdafx.h.gch plugin.*.so
As I mentioned before, those plugin programs should be Position Independent Code, or PIC. The compiler must have the option of "-fPIC".

The file, "stdafx.h", looks like this:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// stdafx.h written by JaeHyukKwak
#include <syslog.h>
#include <string>
#include <fstream>
#include <stdexcept> // runtime_error
#include <memory>   // shared_ptr
#include <string.h> // memcpy
using namespace std;

#include <curl/curl.h>
The file, "my_curl.hpp", was always posted on the previous article but I am posting it again with minor changes:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Written by Jae Hyuk Kwak, raspberrypiprogramming.blogspot.com
class my_curl_t
{
    unique_ptr< CURL, decltype( curl_easy_cleanup )* > curl_;
    string html_;   // temporary buffer to store HTML content
    string put_;

public:
    my_curl_t() : curl_( curl_easy_init(), curl_easy_cleanup )
    {
        if ( nullptr == curl_ ) throw runtime_error( "curl_easy_init() failed." );
        SetOpt_( CURLOPT_WRITEDATA, &html_ );
        SetOpt_( CURLOPT_WRITEFUNCTION, WriteMemoryCallback );
        SetOpt_( CURLOPT_READDATA, &put_ );
        SetOpt_( CURLOPT_READFUNCTION, ReadDataCallback );
    }

    string RequestGet() { return Request_( false ); }
    string RequestPut() { return Request_( true ); }

    void SetOpt_URL( string url ) { SetOpt_( CURLOPT_URL, url.c_str() ); }
    void SetOpt_READDATA( string data ) { put_ = data; }

private:
    string Request_( bool put )
    {
        SetOpt_( CURLOPT_UPLOAD, put );
        const CURLcode res = curl_easy_perform( curl_.get() );
        if ( CURLE_OK != res )
            throw runtime_error( string( "curl_easy_perform() failed: " ) + curl_easy_strerror( res ) );
        const string htmlCopied = html_;
        html_.clear();
        return htmlCopied;
    }

    template< typename ParamType >
    void SetOpt_( CURLoption option, const ParamType &parameter )
    {
        CURLcode res = curl_easy_setopt( curl_.get(), option, parameter );
        if ( CURLE_OK != res )
            throw runtime_error( string( "curl_easy_setopt failed: " ) + curl_easy_strerror( res ) );
    }

    static size_t WriteMemoryCallback( void *contents, size_t size, size_t numberOfMemoryBlock, void *userChunk )
    {
        const size_t realSize = size * numberOfMemoryBlock;
        string &html = *static_cast< string * >( userChunk );
        html += string( static_cast< char* >( contents ), realSize );
        return realSize;
    }

    static size_t ReadDataCallback( char *buf, size_t size, size_t nitems, void *instream )
    {
        string &put = *static_cast< string * >( instream );
        const size_t putSize = put.size();
        const size_t minSize = min( putSize, size * nitems );
        memcpy( buf, put.c_str(), minSize );
        if ( minSize == putSize ) put.clear();
        else put = put.substr( minSize );
        return minSize;
    }
};
That is it.

No comments:

Post a Comment

About Me

My photo
Tomorrow may not come, so I want to do my best now.