Files
OpenMQTT-Gateway/lib/ESPiLight/pilight/libs/openzwave/command_classes/CommandClass.cpp
T
Florian 67dd7ac078 update esppilight lib
content of the library is not filtered on 433mhz only
2018-11-28 20:41:37 +01:00

806 lines
22 KiB
C++

//-----------------------------------------------------------------------------
//
// CommandClass.cpp
//
// Base class for all Z-Wave Command Classes
//
// Copyright (c) 2010 Mal Lansell <openzwave@lansell.org>
//
// SOFTWARE NOTICE AND LICENSE
//
// This file is part of OpenZWave.
//
// OpenZWave is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// OpenZWave is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenZWave. If not, see <http://www.gnu.org/licenses/>.
//
//-----------------------------------------------------------------------------
#include <math.h>
#include <locale.h>
#include "../tinyxml.h"
#include "CommandClass.h"
#include "Basic.h"
#include "MultiInstance.h"
#include "CommandClasses.h"
#include "../Msg.h"
#include "../Node.h"
#include "../Driver.h"
#include "../Manager.h"
#include "../platform/Log.h"
#include "../value_classes/ValueStore.h"
using namespace OpenZWave;
static uint8 const c_sizeMask = 0x07;
static uint8 const c_scaleMask = 0x18;
static uint8 const c_scaleShift = 0x03;
static uint8 const c_precisionMask = 0xe0;
static uint8 const c_precisionShift = 0x05;
//-----------------------------------------------------------------------------
// <CommandClass::CommandClass>
// Constructor
//-----------------------------------------------------------------------------
CommandClass::CommandClass
(
uint32 const _homeId,
uint8 const _nodeId
):
m_homeId( _homeId ),
m_nodeId( _nodeId ),
m_version( 1 ),
m_afterMark( false ),
m_createVars( true ),
m_overridePrecision( -1 ),
m_getSupported( true ),
m_isSecured( false ),
m_SecureSupport( true ),
m_staticRequests( 0 ),
m_sentCnt( 0 ),
m_receivedCnt( 0 )
{
}
//-----------------------------------------------------------------------------
// <CommandClass::~CommandClass>
// Destructor
//-----------------------------------------------------------------------------
CommandClass::~CommandClass
(
)
{
while( !m_endPointMap.empty() )
{
map<uint8,uint8>::iterator it = m_endPointMap.begin();
m_endPointMap.erase( it );
}
while ( !m_RefreshClassValues.empty() )
{
for (unsigned int i = 0; i < m_RefreshClassValues.size(); i++)
{
RefreshValue *rcc = m_RefreshClassValues.at(i);
while(!rcc->RefreshClasses.empty()) {
delete rcc->RefreshClasses.back();
rcc->RefreshClasses.pop_back();
}
// for (unsigned int j = 0; j < rcc->RefreshClasses.size(); i++)
// {
// delete rcc->RefreshClasses[j];
// }
rcc->RefreshClasses.clear();
delete rcc;
}
m_RefreshClassValues.clear();
}
}
//-----------------------------------------------------------------------------
// <CommandClass::GetDriver>
// Get a pointer to our driver
//-----------------------------------------------------------------------------
Driver* CommandClass::GetDriver
(
)const
{
return( Manager::Get()->GetDriver( m_homeId ) );
}
//-----------------------------------------------------------------------------
// <CommandClass::GetNode>
// Get a pointer to our node without locking the mutex
//-----------------------------------------------------------------------------
Node* CommandClass::GetNodeUnsafe
(
)const
{
return( GetDriver()->GetNodeUnsafe( m_nodeId ) );
}
//-----------------------------------------------------------------------------
// <CommandClass::GetValue>
// Get a pointer to a value by its instance and index
//-----------------------------------------------------------------------------
Value* CommandClass::GetValue
(
uint8 const _instance,
uint8 const _index
)
{
Value* value = NULL;
if( Node* node = GetNodeUnsafe() )
{
value = node->GetValue( GetCommandClassId(), _instance, _index );
}
return value;
}
//-----------------------------------------------------------------------------
// <CommandClass::RemoveValue>
// Remove a value by its instance and index
//-----------------------------------------------------------------------------
bool CommandClass::RemoveValue
(
uint8 const _instance,
uint8 const _index
)
{
if( Node* node = GetNodeUnsafe() )
{
return node->RemoveValue( GetCommandClassId(), _instance, _index );
}
return false;
}
//-----------------------------------------------------------------------------
// <CommandClass::SetInstances>
// Instances as set by the MultiInstance V1 command class
//-----------------------------------------------------------------------------
void CommandClass::SetInstances
(
uint8 const _instances
)
{
// Ensure we have a set of reported variables for each new instance
if( !m_afterMark )
{
for( uint8 i=0; i<_instances; ++i )
{
SetInstance( i+1 );
}
}
}
//-----------------------------------------------------------------------------
// <CommandClass::SetInstance>
// Instances as set by the MultiChannel (i.e. MultiInstance V2) command class
//-----------------------------------------------------------------------------
void CommandClass::SetInstance
(
uint8 const _endPoint
)
{
if( !m_instances.IsSet( _endPoint ) )
{
m_instances.Set( _endPoint );
if( IsCreateVars() )
{
CreateVars( _endPoint );
}
}
}
//-----------------------------------------------------------------------------
// <CommandClass::ReadXML>
// Read the saved command class data
//-----------------------------------------------------------------------------
void CommandClass::ReadXML
(
TiXmlElement const* _ccElement
)
{
int32 intVal;
char const* str;
if( TIXML_SUCCESS == _ccElement->QueryIntAttribute( "version", &intVal ) )
{
m_version = (uint8)intVal;
}
uint8 instances = 1;
if( TIXML_SUCCESS == _ccElement->QueryIntAttribute( "instances", &intVal ) )
{
instances = (uint8)intVal;
}
if( TIXML_SUCCESS == _ccElement->QueryIntAttribute( "request_flags", &intVal ) )
{
m_staticRequests = (uint8)intVal;
}
if( TIXML_SUCCESS == _ccElement->QueryIntAttribute( "override_precision", &intVal ) )
{
m_overridePrecision = (int8)intVal;
}
str = _ccElement->Attribute( "after_mark" );
if( str )
{
m_afterMark = !strcmp( str, "true" );
}
str = _ccElement->Attribute( "create_vars" );
if( str )
{
m_createVars = !strcmp( str, "true" );
}
// Make sure previously created values are removed if create_vars=false
if( !m_createVars )
{
if( Node* node = GetNodeUnsafe() )
{
node->GetValueStore()->RemoveCommandClassValues( GetCommandClassId() );
}
}
str = _ccElement->Attribute( "getsupported" );
if( str )
{
m_getSupported = !strcmp( str, "true" );
}
str = _ccElement->Attribute( "issecured" );
if( str )
{
m_isSecured = !strcmp( str, "true" );
}
// Setting the instance count will create all the values.
SetInstances( instances );
// Apply any differences from the saved XML to the values
TiXmlElement const* child = _ccElement->FirstChildElement();
while( child )
{
str = child->Value();
if( str )
{
if( !strcmp( str, "Instance" ) )
{
uint8 instance = 0;
// Add an instance to the command class
if( TIXML_SUCCESS == child->QueryIntAttribute( "index", &intVal ) )
{
instance = (uint8)intVal;
SetInstance( instance );
}
// See if its associated endpoint is present
if( TIXML_SUCCESS == child->QueryIntAttribute( "endpoint", &intVal ) )
{
uint8 endpoint = (uint8)intVal;
SetEndPoint( instance, endpoint );
}
}
else if( !strcmp( str, "Value" ) )
{
// Apply any differences from the saved XML to the value
GetNodeUnsafe()->ReadValueFromXML( GetCommandClassId(), child );
}
else if (!strcmp( str, "TriggerRefreshValue" ) )
{
ReadValueRefreshXML(child);
}
}
child = child->NextSiblingElement();
}
}
//-----------------------------------------------------------------------------
// <CommandClass::ReadValueRefreshXML>
// Read the config that contains a list of Values that should be refreshed when
// we recieve u updated Value from a device. (This helps Yale Door Locks, which send a
// Alarm Report instead of DoorLock Report when the status of the Door Lock is changed
//-----------------------------------------------------------------------------
void CommandClass::ReadValueRefreshXML
(
TiXmlElement const* _ccElement
)
{
char const* str;
bool ok = false;
const char *genre;
RefreshValue *rcc = new RefreshValue();
rcc->cc = GetCommandClassId();
genre = _ccElement->Attribute( "Genre" );
rcc->genre = Value::GetGenreEnumFromName(genre);
_ccElement->QueryIntAttribute( "Instance", (int*)&rcc->instance);
_ccElement->QueryIntAttribute( "Index", (int*)&rcc->index);
Log::Write(LogLevel_Info, GetNodeId(), "Value Refresh triggered by CommandClass: %s, Genre: %d, Instance: %d, Index: %d for:", GetCommandClassName().c_str(), rcc->genre, rcc->instance, rcc->index);
TiXmlElement const* child = _ccElement->FirstChildElement();
while( child )
{
str = child->Value();
if( str )
{
if ( !strcmp(str, "RefreshClassValue"))
{
RefreshValue *arcc = new RefreshValue();
if (child->QueryIntAttribute( "CommandClass", (int*)&arcc->cc) != TIXML_SUCCESS) {
Log::Write(LogLevel_Warning, GetNodeId(), " Invalid XML - CommandClass Attribute is wrong type or missing");
continue;
}
if (child->QueryIntAttribute( "RequestFlags", (int*)&arcc->genre) != TIXML_SUCCESS) {
Log::Write(LogLevel_Warning, GetNodeId(), " Invalid XML - RequestFlags Attribute is wrong type or missing");
continue;
}
if (child->QueryIntAttribute( "Instance", (int*)&arcc->instance) != TIXML_SUCCESS) {
Log::Write(LogLevel_Warning, GetNodeId(), " Invalid XML - Instance Attribute is wrong type or missing");
continue;
}
if (child->QueryIntAttribute( "Index", (int*)&arcc->index) != TIXML_SUCCESS) {
Log::Write(LogLevel_Warning, GetNodeId(), " Invalid XML - Index Attribute is wrong type or missing");
continue;
}
Log::Write(LogLevel_Info, GetNodeId(), " CommandClass: %s, RequestFlags: %d, Instance: %d, Index: %d", CommandClasses::GetName(arcc->cc).c_str(), arcc->genre, arcc->instance, arcc->index);
rcc->RefreshClasses.push_back(arcc);
ok = true;
}
else
{
Log::Write(LogLevel_Warning, GetNodeId(), "Got Unhandled Child Entry in TriggerRefreshValue XML Config: %s", str);
}
}
child = child->NextSiblingElement();
}
if (ok == true) {
m_RefreshClassValues.push_back( rcc );
} else {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to add a RefreshClassValue from XML");
delete rcc;
}
}
//-----------------------------------------------------------------------------
// <CommandClass::CheckForRefreshValues>
// Scan our m_RefreshClassValues vector to see if we should call any other
// Command Classes to refresh their value
//-----------------------------------------------------------------------------
bool CommandClass::CheckForRefreshValues (
Value const* _value
)
{
if (m_RefreshClassValues.empty())
{
//Log::Write(LogLevel_Debug, GetNodeId(), "Bailing out of CheckForRefreshValues");
return false;
}
Node* node = GetNodeUnsafe();
if( node != NULL )
{
for (uint32 i = 0; i < m_RefreshClassValues.size(); i++)
{
RefreshValue *rcc = m_RefreshClassValues.at(i);
//Log::Write(LogLevel_Debug, GetNodeId(), "Checking Value Against RefreshClassList: CommandClass %s = %s, Genre %d = %d, Instance %d = %d, Index %d = %d", CommandClasses::GetName(rcc->cc).c_str(), CommandClasses::GetName(_value->GetID().GetCommandClassId()).c_str(), rcc->genre, _value->GetID().GetGenre(), rcc->instance, _value->GetID().GetInstance(), rcc->index, _value->GetID().GetIndex());
if ((rcc->genre == _value->GetID().GetGenre()) && (rcc->instance == _value->GetID().GetInstance()) && (rcc->index == _value->GetID().GetIndex()) )
{
/* we got a match..... */
for (uint32 j = 0; j < rcc->RefreshClasses.size(); j++)
{
RefreshValue *arcc = rcc->RefreshClasses.at(j);
Log::Write(LogLevel_Debug, GetNodeId(), "Requesting Refresh of Value: CommandClass: %s Genre %d, Instance %d, Index %d", CommandClasses::GetName(arcc->cc).c_str(), arcc->genre, arcc->instance, arcc->index);
if( CommandClass* cc = node->GetCommandClass( arcc->cc ) )
{
cc->RequestValue(arcc->genre, arcc->index, arcc->instance, Driver::MsgQueue_Send);
}
}
}
}
}
else /* Driver */
{
Log::Write(LogLevel_Warning, GetNodeId(), "Can't get Node");
}
return true;
}
//-----------------------------------------------------------------------------
// <CommandClass::WriteXML>
// Save the static node configuration data
//-----------------------------------------------------------------------------
void CommandClass::WriteXML
(
TiXmlElement* _ccElement
)
{
char str[32];
snprintf( str, sizeof(str), "%d", GetCommandClassId() );
_ccElement->SetAttribute( "id", str );
_ccElement->SetAttribute( "name", GetCommandClassName().c_str() );
snprintf( str, sizeof(str), "%d", GetVersion() );
_ccElement->SetAttribute( "version", str );
if( m_staticRequests )
{
snprintf( str, sizeof(str), "%d", m_staticRequests );
_ccElement->SetAttribute( "request_flags", str );
}
if( m_overridePrecision >= 0 )
{
snprintf( str, sizeof(str), "%d", m_overridePrecision );
_ccElement->SetAttribute( "override_precision", str );
}
if( m_afterMark )
{
_ccElement->SetAttribute( "after_mark", "true" );
}
if( !m_createVars )
{
_ccElement->SetAttribute( "create_vars", "false" );
}
if( !m_getSupported )
{
_ccElement->SetAttribute( "getsupported", "false" );
}
if ( m_isSecured )
{
_ccElement->SetAttribute( "issecured", "true" );
}
// Write out the instances
for( Bitfield::Iterator it = m_instances.Begin(); it != m_instances.End(); ++ it )
{
TiXmlElement* instanceElement = new TiXmlElement( "Instance" );
_ccElement->LinkEndChild( instanceElement );
snprintf( str, sizeof(str), "%d", *it );
instanceElement->SetAttribute( "index", str );
map<uint8,uint8>::iterator eit = m_endPointMap.find( *it );
if( eit != m_endPointMap.end() )
{
snprintf( str, sizeof(str), "%d", eit->second );
instanceElement->SetAttribute( "endpoint", str );
}
}
// Write out the values for this command class
ValueStore* store = GetNodeUnsafe()->GetValueStore();
for( ValueStore::Iterator it = store->Begin(); it != store->End(); ++it )
{
Value* value = it->second;
if( value->GetID().GetCommandClassId() == GetCommandClassId() )
{
TiXmlElement* valueElement = new TiXmlElement( "Value" );
_ccElement->LinkEndChild( valueElement );
value->WriteXML( valueElement );
}
}
// Write out the TriggerRefreshValue if it exists
for (uint32 i = 0; i < m_RefreshClassValues.size(); i++)
{
RefreshValue *rcc = m_RefreshClassValues.at(i);
TiXmlElement* RefreshElement = new TiXmlElement("TriggerRefreshValue");
_ccElement->LinkEndChild( RefreshElement );
RefreshElement->SetAttribute("Genre", Value::GetGenreNameFromEnum((ValueID::ValueGenre)rcc->genre));
RefreshElement->SetAttribute("Instance", rcc->instance);
RefreshElement->SetAttribute("Index", rcc->index);
for (uint32 j = 0; j < rcc->RefreshClasses.size(); j++)
{
RefreshValue *arcc = rcc->RefreshClasses.at(j);
TiXmlElement *ClassElement = new TiXmlElement("RefreshClassValue");
RefreshElement->LinkEndChild(ClassElement);
ClassElement->SetAttribute("CommandClass", arcc->cc);
ClassElement->SetAttribute("RequestFlags", arcc->genre);
ClassElement->SetAttribute("Instance", arcc->instance);
ClassElement->SetAttribute("Index", arcc->index);
}
}
}
//-----------------------------------------------------------------------------
// <CommandClass::ExtractValue>
// Read a value from a variable length sequence of bytes
//-----------------------------------------------------------------------------
string CommandClass::ExtractValue
(
uint8 const* _data,
uint8* _scale,
uint8* _precision,
uint8 _valueOffset // = 1
)const
{
uint8 const size = _data[0] & c_sizeMask;
uint8 const precision = (_data[0] & c_precisionMask) >> c_precisionShift;
if( _scale )
{
*_scale = (_data[0] & c_scaleMask) >> c_scaleShift;
}
if( _precision )
{
*_precision = precision;
}
uint32 value = 0;
uint8 i;
for( i=0; i<size; ++i )
{
value <<= 8;
value |= (uint32)_data[i+(uint32)_valueOffset];
}
// Deal with sign extension. All values are signed
string res;
if( _data[_valueOffset] & 0x80 )
{
res = "-";
// MSB is signed
if( size == 1 )
{
value |= 0xffffff00;
}
else if( size == 2 )
{
value |= 0xffff0000;
}
}
// Convert the integer to a decimal string. We avoid
// using floats to prevent accuracy issues.
char numBuf[12] = {0};
if( precision == 0 )
{
// The precision is zero, so we can just print the number directly into the string.
snprintf( numBuf, 12, "%d", (signed int)value );
res = numBuf;
}
else
{
// We'll need to insert a decimal point and include any necessary leading zeros.
// Fill the buffer with the value padded with leading zeros.
snprintf( numBuf, 12, "%011d", (signed int)value );
// Calculate the position of the decimal point in the buffer
int32 decimal = 10-precision;
// Shift the characters to make space for the decimal point.
// We don't worry about overwriting any minus sign since that is
// already written into the res string. While we're shifting, we
// also look for the real starting position of the number so we
// can copy it into the res string later.
int32 start = -1;
for( int32 i=0; i<decimal; ++i )
{
numBuf[i] = numBuf[i+1];
if( ( start<0 ) && ( numBuf[i] != '0' ) )
{
start = i;
}
}
if( start<0 )
{
start = decimal-1;
}
// Insert the decimal point
struct lconv const* locale = localeconv();
numBuf[decimal] = *(locale->decimal_point);
// Copy the buffer into res
res += &numBuf[start];
}
return res;
}
//-----------------------------------------------------------------------------
// <CommandClass::AppendValue>
// Add a value to a message as a sequence of bytes
//-----------------------------------------------------------------------------
void CommandClass::AppendValue
(
Msg* _msg,
string const& _value,
uint8 const _scale
)const
{
uint8 precision;
uint8 size;
int32 val = ValueToInteger( _value, &precision, &size );
_msg->Append( (precision<<c_precisionShift) | (_scale<<c_scaleShift) | size );
int32 shift = (size-1)<<3;
for( int32 i=size; i>0; --i, shift-=8 )
{
_msg->Append( (uint8)(val >> shift) );
}
}
//-----------------------------------------------------------------------------
// <CommandClass::GetAppendValueSize>
// Get the number of bytes that would be added by a call to AppendValue
//-----------------------------------------------------------------------------
uint8 const CommandClass::GetAppendValueSize
(
string const& _value
)const
{
uint8 size;
ValueToInteger( _value, NULL, &size );
return size;
}
//-----------------------------------------------------------------------------
// <CommandClass::ValueToInteger>
// Convert a decimal string to an integer and report the precision and
// number of bytes required to store the value.
//-----------------------------------------------------------------------------
int32 CommandClass::ValueToInteger
(
string const& _value,
uint8* o_precision,
uint8* o_size
)const
{
int32 val;
uint8 precision;
// Find the decimal point
size_t pos = _value.find_first_of( "." );
if( pos == string::npos )
pos = _value.find_first_of( "," );
if( pos == string::npos )
{
// No decimal point
precision = 0;
// Convert the string to an integer
val = atol( _value.c_str() );
}
else
{
// Remove the decimal point and convert to an integer
precision = (uint8) ((_value.size()-pos)-1);
string str = _value.substr( 0, pos ) + _value.substr( pos+1 );
val = atol( str.c_str() );
}
if ( m_overridePrecision > 0 )
{
while ( precision < m_overridePrecision ) {
precision++;
val *= 10;
}
}
if ( o_precision ) *o_precision = precision;
if( o_size )
{
// Work out the size as either 1, 2 or 4 bytes
*o_size = 4;
if( val < 0 )
{
if( ( val & 0xffffff80 ) == 0xffffff80 )
{
*o_size = 1;
}
else if( ( val & 0xffff8000 ) == 0xffff8000 )
{
*o_size = 2;
}
}
else
{
if( ( val & 0xffffff00 ) == 0 )
{
*o_size = 1;
}
else if( ( val & 0xffff0000 ) == 0 )
{
*o_size = 2;
}
}
}
return val;
}
//-----------------------------------------------------------------------------
// <CommandClass::UpdateMappedClass>
// Update the mapped class if there is one with BASIC level
//-----------------------------------------------------------------------------
void CommandClass::UpdateMappedClass
(
uint8 const _instance,
uint8 const _classId,
uint8 const _level
)
{
if( _classId )
{
if( Node* node = GetNodeUnsafe() )
{
CommandClass* cc = node->GetCommandClass( _classId );
if( node->GetCurrentQueryStage() == Node::QueryStage_Complete && cc != NULL )
{
cc->SetValueBasic( _instance, _level );
}
}
}
}
//-----------------------------------------------------------------------------
// <CommandClass::ClearStaticRequest>
// The static data for this command class has been read from the device
//-----------------------------------------------------------------------------
void CommandClass::ClearStaticRequest
(
uint8 _request
)
{
m_staticRequests &= ~_request;
}
//-----------------------------------------------------------------------------
// <CommandClass::RequestStateForAllInstances>
// Request current state from the device
//-----------------------------------------------------------------------------
bool CommandClass::RequestStateForAllInstances
(
uint32 const _requestFlags,
Driver::MsgQueue const _queue
)
{
bool res = false;
if( m_createVars )
{
if( Node* node = GetNodeUnsafe() )
{
MultiInstance* multiInstance = static_cast<MultiInstance*>( node->GetCommandClass( MultiInstance::StaticGetCommandClassId() ) );
if( multiInstance != NULL )
{
for( Bitfield::Iterator it = m_instances.Begin(); it != m_instances.End(); ++it )
{
res |= RequestState( _requestFlags, (uint8)*it, _queue );
}
}
else
{
res = RequestState( _requestFlags, 1, _queue );
}
}
}
return res;
}