Wednesday, November 5, 2014

ZWay in C++: how to send command

This post continues after my previous post: ZWay in C++ how to access data. This post will show how to send command to ZWave device with ZWay library.

This program is based on a ZWave binary power switch I bought but it shouldn't be hard to apply the same to any other ZWave devices.

 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
// myzway.cpp written by Jae Hyuk Kwak
#include "stdafx.h"
#include "WaitForSignal.hpp"
#include "MyZWay.hpp"
#include "MyZWayCommandEnum.hpp"
#include "MyZWayGet.hpp"
#include "MyZWaySend.hpp"
#include "config.hpp"
using namespace Configuration;
using namespace MyZWayNS;
int main( int argc, char *argv[] )
{
    Init zway( devPath, configPath, translationsPath, ZDDXPath );

    Device raz( &zway, 1 );
    Device sensor( &zway, 2 );
    Device power( &zway, 3 );
    Device repeater( &zway, 4 );

    cout << "1: " << Get< string >( &raz, "deviceTypeString" ) << endl;
    cout << "2: " << Get< string >( &sensor, "deviceTypeString" ) << endl;
    cout << "3: " << Get< string >( &power, "deviceTypeString" ) << endl;
    cout << "4: " << Get< string >( &repeater, "deviceTypeString" ) << endl;

    Instance power0( &power, 0 );

    const bool before = Get< bool >( &power0, SWITCH_BINARY_GET, "level" );
    cout << "Power of device 3 is " << ( before ? "on" : "off" ) << endl;

    Send< SWITCH_BINARY_SET >( &power0, true );

    const bool after = Get< bool >( &power0, SWITCH_BINARY_GET, "level" );
    cout << "Power of device 3 is " << ( after ? "on" : "off" ) << endl;
    return 0;
}
The main function does almost same thing that the previous program does but now it also send a command to switch on the power switch.

There are bunch of ZWave command classes according to ZWave specification. Each of them is represented by unique number. You can find those numbers of ZWay document. For example, Power Switch command is 37, which is 0x25 in Hex. I took the numbers and made a enum, ZWayCommandEnum. In fact, the enum numbers doesn't need to match to ZWay internal command number but I thought that this is more meaningful numbers than just randomly I assign them.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// MyZWayCommandEnum.hpp written by Jae Hyuk Kwak
namespace MyZWayNS
{
    enum CommandEnum
    {
        SET_BIT_MASK = 0X100,
        REMOVE_BIT_MASK = 0X200,

        BASIC_GET = ( 0X20 ),
        BASIC_SET = ( BASIC_GET | SET_BIT_MASK ),

        SWITCH_BINARY_GET = ( 0X25 ),
        SWITCH_BINARY_SET = ( SWITCH_BINARY_GET | SET_BIT_MASK ),

        SENSOR_BINARY_GET = ( 0X30 ), // there is no set counter-part

        ASSOCIATION_REMOVE = ( 0X85 | REMOVE_BIT_MASK ),
    };
}

 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
63
64
// MyZWaySend.hpp written by Jae Hyuk Kwak
namespace MyZWayNS
{
    namespace ImplementationNS
    {
        struct CallbackUser // Callback Userdata
        {
            Instance * const inst;      // include "MyZWayGet.hpp"
            WaitForSignal * const wcb;  // include "WaitForSignal.hpp"
        public:
            CallbackUser( Instance *inst, WaitForSignal *wcb )
                : inst( inst ), wcb( wcb )
            {}
        };

        template< bool succeed >
        void Callback( ZWay zway, ZWBYTE, void *userData )
        {
            if ( succeed )
                cout << "Send succeed." << endl;
            else
                cerr << "Send failed." << endl;
            static_cast< CallbackUser * >( userData )->wcb->WaitIsOver();
        }

        template< typename ZWayFuncType, typename... ArgTypes >
        void TSend( CallbackUser &so, ZWayFuncType zwayFunc, ArgTypes... args )
        {
            zwayFunc( so.inst->zway, so.inst->deviceId, so.inst->instanceId, args..., Callback< true >, Callback< false >, static_cast< void * >( &so ) );
        }

        template< CommandEnum, typename... ArgTypes >
        void Send( CallbackUser &, ArgTypes... args );

        template<>
        void Send< BASIC_GET >( CallbackUser &so )
        { TSend( so, zway_cc_basic_get ); }

        template<>
        void Send< BASIC_SET >( CallbackUser &so, bool value )
        { TSend( so, zway_cc_basic_set, value ? 1 : 0 ); }

        template<>
        void Send< SWITCH_BINARY_GET >( CallbackUser &so )
        { TSend( so, zway_cc_switch_binary_get ); }

        template<>
        void Send< SWITCH_BINARY_SET >( CallbackUser &so, bool value )
        { TSend( so, zway_cc_switch_binary_set, value ? 1 : 0 ); }

        template<>
        void Send< SENSOR_BINARY_GET >( CallbackUser &so )
        { TSend( so, zway_cc_sensor_binary_get, -1 ); }
    }

    template< CommandEnum cmdId, typename... ArgTypes >
    void Send( Instance *inst, ArgTypes... args )
    {
        using namespace ImplementationNS;
        WaitForSignal wcb; // destructor will wait for callback
        CallbackUser so( inst, &wcb );
        Send< cmdId >( so, args... );
    }
}
This class may seem complex again with templates. But the basic idea is same with MyZWayGet. I didn't like the weak-type style so I made it strong-type with template.

All of those zway_cc_XXX functions take two call back functions. The first one will be called when the command processing was successful and the second call back will be called when the processing went wrong. I am sure what can make the execution fail but I do get the failed cases occasionally. Initially I was throwing an exception in the function, "callback< false >", but since I don't know why it fails, I replaced it with cerr. I think it fails when the ZWave signal is too weak.

The call-back function takes three arguments. The 3rd one is supposed to be whatever is given when the zway_cc_XXX functions are called. This call-back with a custom argument is a common patter with call-back style programming but it can go pretty wrong if muti-thread is envolved. If I give an object pointer as the argument, the object must be valid at the time the call-back function is called from the other side.

One more thing to be careful with the call-back functions is that when those call-back functions are called from zway system, the zway is locked for a ZDataHolder. It means that call-back functions should not try to acquire another lock on zway system. In other words, call-back function can do only limited things in the life time.

Also when the callback function is called, it is called from another thread. So be prepared for race condition.

 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
// WaitForSignal.hpp written by Jae Hyuk Kwak
class WaitForSignal
{
    mutex mtx_;
    condition_variable cv_;
    unique_lock< mutex > lock_;
    atomic< bool > waitIsOver_;
public:
    WaitForSignal()
        : lock_( mtx_ )
    {
        waitIsOver_.store( false, memory_order_release );
    }

    ~WaitForSignal()
    {
        cv_.wait( lock_, [this]
        {
            return waitIsOver_.load( memory_order_acquire );
        } );
    }

    void WaitIsOver()
    {
        unique_lock< mutex > lock( mtx_ );
        waitIsOver_.store( true, memory_order_release );
        lock.unlock();
        cv_.notify_one();
    }
};
The class WaitForSignal is to make sure that the main thread waits for any callback function to be called. If the main thread quit or the application end before the callback functions are called, sent command may not complete the transaction; I don't know exactly what's gonna happen if the command is aborted in the middle abruptly.
pi@fileserver 10:43:32 myzway$ make
g++-4.7 -Wall -g -std=c++11 -I/opt/z-way-server/libzway-dev/ -L/opt/z-way-server/libs -c stdafx.h
g++-4.7 -Wall -g -std=c++11 -I/opt/z-way-server/libzway-dev/ -L/opt/z-way-server/libs -o myzway main.cpp -lzway -lxml2 -larchive -lssl -Wl,-rpath=/opt/z-way-server/libs
pi@fileserver 10:44:03 myzway$ ./myzway 
1: Static PC Controller
2: Routing Binary Sensor
3: Binary Power Switch
4: Repeater Slave
Power of device 3 is off
Send succeed.
Power of device 3 is on
Once you got the program, you can compile it and run it. Note that I have experienced that sometimes the send command fail depending on the strength of the ZWave signal.

No comments:

Post a Comment

About Me

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