Wednesday, November 5, 2014

ZWay in C++: how to access data

This post is continued from my previous post: "ZWay in C++: initialization". This post will show how to read information from ZWay system.

I have only three devices that has ZWave chips: RaZberry, Door/Window sensor and Power switch. I intended to turn on something with the power switch when somebody open the door. In my setting, device ID is as following:
  • RaZberry got id 1
  • Door/Window sensor got id 2
  • Power switch got id 3
  • Signal extender got id 4

 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
// myzway.cpp written by Jae Hyuk Kwak
#include "stdafx.h" 
#include "MyZWay.hpp"
#include "MyZWayCommandEnum.hpp"
#include "MyZWayGet.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 level = Get< bool >( &power0, SWITCH_BINARY_GET, "level" );
    cout << "Power of device 3 is " << ( level ? "on" : "off" ) << endl;
    return 0;
}  

The intention of the program above is to print out the device description that is stored on each device. Also it checks if the power switch status is currently "on" or "off". This checking may not work for now if there is a big network delay or something. As I mentioned earlier, there are two types of ZWave devices. One is devices that are turned on all the time and another is devices that are in sleep mode most of time but wake up occasionally or by some events. Power switch I have is the first type, which means it is turned on all the time. If I try to get the device status from Door/Window sensor, ZWay will give me whatever it stores currently but the value is probably updated last time the device woke up. It may be still fine to use it but if ZWay was not running when the device woke up last time, ZWay might miss the value and it goes out of sync. A proper way to get the device status is to follow steps:
  1. Send value update request to ZWay server.
  2. Wait for ZWay server to process the communiation because there might be other things ZWay may be doing at the moment.
  3. Wait for the device to wake up, which is set to 5minute in my case.
  4. Wait for ZWay to execute a call back function that I gave.

This characteristics of sleeping devices is very important to understand. Otherwise you may make mistake on your program due to the nature of asynchronous communication.
 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// MyZWayGet.hpp written by Jae Hyuk Kwak
namespace MyZWayNS
{
    static_assert( sizeof( MyZWayNS::Init ) > 0, "Include MyZWay.hpp first" );
    namespace ImplementationNS
    {
        class DataLock
        {
            ZWay    zway_;
        public:
            DataLock( ZWay zway ) : zway_( zway )
            {
                WaitUntilAvailable( zway_ );
                zway_data_acquire_lock( zway_ );    // required for ZDataHolder
            }
            ~DataLock() { zway_data_release_lock( zway_ ); }
        };

        // runtime zway type checking and retrieving data
        template< typename ReturnType, ZWDataType expectedType, typename ZWayType, typename GetterFuncType >
        ZWayType TGet( ZWay zway, ZDataHolder dh, GetterFuncType getterFunc )
        {
            if ( const bool enableTypeChecking = true )
            {
                ZWDataType actualType;
                zway_data_get_type( zway, dh, &actualType );
                if ( expectedType != actualType )
                    throw runtime_error( "Unexpected data type" );
            }
            ZWayType val;
            getterFunc( zway, dh, &val );
            return val;
        }

        // type conversion from C++ standard types to ZWay types
        template< typename ReturnType >
        ReturnType Get( ZWay zway, ZDataHolder dh );

        template<>
        string Get< string >( ZWay zway, ZDataHolder dh )
        { return TGet< string, String, ZWCSTR >( zway, dh, zway_data_get_string ); }

        template<>
        bool Get< bool >( ZWay zway, ZDataHolder dh )
        { return TGet< bool, Boolean, ZWBOOL >( zway, dh, zway_data_get_boolean ); }
    }
    struct Device
    {
        const ZWay zway;
        const ZWBYTE deviceId;

        Device( Init *init, ZWBYTE deviceId )
            : zway( init->native() )
            , deviceId( deviceId )
        {}
    };

    struct Instance : Device
    {
        const ZWBYTE instanceId;

        Instance( Init *init, ZWBYTE deviceId, ZWBYTE instanceId )
            : Device( init, deviceId ), instanceId( instanceId )
        {}

        Instance( Device *device, ZWBYTE instanceId )
            : Device( *device ), instanceId( instanceId )
        {}
    };

    // User function for Device data
    template< typename ReturnType >
    ReturnType Get( Device *device, const char *path )
    {
        ImplementationNS::DataLock lock( device->zway );
        ZDataHolder dh = zway_find_device_data( device->zway, device->deviceId, path );
        if ( nullptr == dh ) throw runtime_error( "Cannot find device data" );
        return ImplementationNS::Get< ReturnType >( device->zway, dh );
    }

    // User function for Command data
    template< typename ReturnType >
    ReturnType Get( Instance *inst, CommandEnum cmdId, const char *path )
    {
        ImplementationNS::DataLock lock( inst->zway );
        ZDataHolder dh = zway_find_device_instance_cc_data( inst->zway, inst->deviceId, inst->instanceId, static_cast< ZWBYTE >( cmdId ), path );
        if ( nullptr == dh ) throw runtime_error( "Cannot find command data" );
        return ImplementationNS::Get< ReturnType >( inst->zway, dh );
    }
}
*PS: stdafx.h, Makefile and config.hpp remain same as the previous post.

The screenshot above shows a class that handles ZDataHolder. Whenever ZDataHolder is accessed, it must acquire exclusive lock on zway system. This is my guess but it must have only one shared data storage internally. Also note that the lock doesn't allow recursive lock. I will explain it later more but in other words, you cannot query data while you are holding other data object.

The main role of the class, MyZWayData, is to make sure the lock is acquired before ZDataHolder object is accessed and to make sure that the lock is release once it is done. This is implemented in the constructor and destructor.

There are other methods that may look over complicated with "template". ZWay system uses weak-typed data like JavaScript does. A data object can be any type at runtime not at compile-time. I didn't like weak-typed data so I made it strong-typed data with template. As a result, the expected return type must be given ahead whenever the data is requested. If the actual data type is different from expected, it will simply throw a runtime exception.

The function "Get" takes "Overload < type >" as the first argument. It is because C++ doesn't allow full explicit specialization inside of class. In other words, I wanted to have method like "template< ReturnType > ReturnType Get( ZDataHolder d );" and specialize for each return type but I couldn't do it. Instead, with "Overload" trick, I can implement different methods for different return types.

I have refactored this class more than ten times already so there are so many different versions of the class remaining in my head still. lol. It is one of the source of my headache when I think about this class.

pi@fileserver 21:51:00 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 21:51:24 myzway$ ./myzway 
1: Static PC Controller
2: Routing Binary Sensor
3: Binary Power Switch
4: Repeater Slave
Power of device 3 is off
pi@fileserver 21:51:27 myzway$ 
Once you made the program, you can compile it and run it.

No comments:

Post a Comment

About Me

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