//----------------------------------------------------------------------------- // // Node.cpp // // A node in the Z-Wave network. // // Copyright (c) 2009 Mal Lansell // // 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 . // //----------------------------------------------------------------------------- #include "Node.h" #include "Defs.h" #include "Group.h" #include "Options.h" #include "Manager.h" #include "Driver.h" #include "Notification.h" #include "Msg.h" #include "platform/Log.h" #include "platform/Mutex.h" #include "tinyxml.h" #include "command_classes/CommandClasses.h" #include "command_classes/CommandClass.h" #include "command_classes/Association.h" #include "command_classes/Basic.h" #include "command_classes/Configuration.h" #include "command_classes/ControllerReplication.h" #include "command_classes/ManufacturerSpecific.h" #include "command_classes/MultiInstance.h" #include "command_classes/Security.h" #include "command_classes/WakeUp.h" #include "command_classes/NodeNaming.h" #include "command_classes/NoOperation.h" #include "command_classes/Version.h" #include "command_classes/SwitchAll.h" #include "Scene.h" #include "value_classes/ValueID.h" #include "value_classes/Value.h" #include "value_classes/ValueBool.h" #include "value_classes/ValueButton.h" #include "value_classes/ValueByte.h" #include "value_classes/ValueDecimal.h" #include "value_classes/ValueInt.h" #include "value_classes/ValueRaw.h" #include "value_classes/ValueList.h" #include "value_classes/ValueSchedule.h" #include "value_classes/ValueShort.h" #include "value_classes/ValueString.h" #include "value_classes/ValueStore.h" using namespace OpenZWave; //----------------------------------------------------------------------------- // Statics //----------------------------------------------------------------------------- bool Node::s_deviceClassesLoaded = false; map Node::s_basicDeviceClasses; map Node::s_genericDeviceClasses; static char const* c_queryStageNames[] = { "ProtocolInfo", "Probe", "WakeUp", "ManufacturerSpecific1", "NodeInfo", "SecurityReport", "ManufacturerSpecific2", "Versions", "Instances", "Static", "Probe1", "Associations", "Neighbors", "Session", "Dynamic", "Configuration", "Complete", "None" }; //----------------------------------------------------------------------------- // // Constructor //----------------------------------------------------------------------------- Node::Node ( uint32 const _homeId, uint8 const _nodeId ): m_queryStage( QueryStage_None ), m_queryPending( false ), m_queryConfiguration( false ), m_queryRetries( 0 ), m_protocolInfoReceived( false ), m_nodeInfoReceived( false ), m_manufacturerSpecificClassReceived( false ), m_nodeInfoSupported( true ), m_nodeAlive( true ), // assome live node m_listening( true ), // assume we start out listening m_frequentListening( false ), m_beaming( false ), m_routing( false ), m_maxBaudRate( 0 ), m_version( 0 ), m_security( false ), m_homeId( _homeId ), m_nodeId( _nodeId ), m_basic( 0 ), m_generic( 0 ), m_specific( 0 ), m_type( "" ), m_numRouteNodes( 0 ), m_addingNode( false ), m_manufacturerName( "" ), m_productName( "" ), m_nodeName( "" ), m_location( "" ), m_manufacturerId( "" ), m_productType( "" ), m_productId( "" ), m_values( new ValueStore() ), m_sentCnt( 0 ), m_sentFailed( 0 ), m_retries( 0 ), m_receivedCnt( 0 ), m_receivedDups( 0 ), m_receivedUnsolicited( 0 ), m_lastRequestRTT( 0 ), m_lastResponseRTT( 0 ), m_averageRequestRTT( 0 ), m_averageResponseRTT( 0 ), m_quality( 0 ), m_lastReceivedMessage(), m_errors( 0 ) { memset( m_neighbors, 0, sizeof(m_neighbors) ); memset( m_routeNodes, 0, sizeof(m_routeNodes) ); AddCommandClass( 0 ); } //----------------------------------------------------------------------------- // // Destructor //----------------------------------------------------------------------------- Node::~Node ( ) { // Remove any messages from queues GetDriver()->RemoveQueues( m_nodeId ); // Remove the values from the poll list for( ValueStore::Iterator it = m_values->Begin(); it != m_values->End(); ++it ) { ValueID const& valueId = it->second->GetID(); if( GetDriver()->isPolled( valueId ) ) { GetDriver()->DisablePoll( valueId ); } } Scene::RemoveValues( m_homeId, m_nodeId ); // Delete the values delete m_values; // Delete the command classes while( !m_commandClassMap.empty() ) { map::iterator it = m_commandClassMap.begin(); delete it->second; m_commandClassMap.erase( it ); } // Delete the groups while( !m_groups.empty() ) { map::iterator it = m_groups.begin(); delete it->second; m_groups.erase( it ); } // Delete the button map while( !m_buttonMap.empty() ) { map::iterator it = m_buttonMap.begin(); m_buttonMap.erase( it ); } } //----------------------------------------------------------------------------- // // Proceed through the initialisation process //----------------------------------------------------------------------------- void Node::AdvanceQueries ( ) { // For OpenZWave to discover everything about a node, we have to follow a certain // order of queries, because the results of one stage may affect what is requested // in the next stage. The stage is saved with the node data, so that any incomplete // queries can be restarted the next time the application runs. // The individual command classes also store some state as to whether they have // had a response to certain queries. This state is initilized by the SetStaticRequests // call in QueryStage_None. It is also saved, so we do not need to request state // from every command class if some have previously responded. // // Each stage must generate all the messages for its particular stage as // assumptions are made in later code (RemoveMsg) that this is the case. This means // each stage is only visited once. Log::Write( LogLevel_Detail, m_nodeId, "AdvanceQueries queryPending=%d queryRetries=%d queryStage=%s live=%d", m_queryPending, m_queryRetries, c_queryStageNames[m_queryStage], m_nodeAlive ); bool addQSC = false; // We only want to add a query stage complete if we did some work. while( !m_queryPending && m_nodeAlive ) { switch( m_queryStage ) { case QueryStage_None: { // Init the node query process m_queryStage = QueryStage_ProtocolInfo; m_queryRetries = 0; break; } case QueryStage_ProtocolInfo: { // determines, among other things, whether this node is a listener, its maximum baud rate and its device classes if( !ProtocolInfoReceived() ) { Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_ProtocolInfo" ); Msg* msg = new Msg( "Get Node Protocol Info", m_nodeId, REQUEST, FUNC_ID_ZW_GET_NODE_PROTOCOL_INFO, false ); msg->Append( m_nodeId ); GetDriver()->SendMsg( msg, Driver::MsgQueue_Query ); m_queryPending = true; addQSC = true; } else { // This stage has been done already, so move to the Neighbours stage m_queryStage = QueryStage_Probe; m_queryRetries = 0; } break; } case QueryStage_Probe: { Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Probe" ); // // Send a NoOperation message to see if the node is awake // and alive. Based on the response or lack of response // will determine next step. // NoOperation* noop = static_cast( GetCommandClass( NoOperation::StaticGetCommandClassId() ) ); if( GetDriver()->GetNodeId() != m_nodeId ) { noop->Set( true ); m_queryPending = true; addQSC = true; } else { m_queryStage = QueryStage_WakeUp; m_queryRetries = 0; } break; } case QueryStage_WakeUp: { // For sleeping devices other than controllers, we need to defer the usual requests until // we have told the device to send it's wake-up notifications to the PC controller. Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_WakeUp" ); WakeUp* wakeUp = static_cast( GetCommandClass( WakeUp::StaticGetCommandClassId() ) ); // if this device is a "sleeping device" and not a controller and not a // FLiRS device. FLiRS will wake up when you send them something and they // don't seem to support Wakeup if( wakeUp && !IsController() && !IsFrequentListeningDevice() ) { // start the process of requesting node state from this sleeping device wakeUp->Init(); m_queryPending = true; addQSC = true; } else { // this is not a sleeping device, so move to the ManufacturerSpecific1 stage m_queryStage = QueryStage_ManufacturerSpecific1; m_queryRetries = 0; } break; } case QueryStage_ManufacturerSpecific1: { // Obtain manufacturer, product type and product ID code from the node device // Manufacturer Specific data is requested before the other command class data so // that we can modify the supported command classes list through the product XML files. Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_ManufacturerSpecific1" ); if( GetDriver()->GetNodeId() == m_nodeId ) { string configPath = ManufacturerSpecific::SetProductDetails( this, GetDriver()->GetManufacturerId(), GetDriver()->GetProductType(), GetDriver()->GetProductId() ); if( configPath.length() > 0 ) { ManufacturerSpecific::LoadConfigXML( this, configPath ); } m_queryStage = QueryStage_NodeInfo; m_queryRetries = 0; } else { ManufacturerSpecific* cc = static_cast( GetCommandClass( ManufacturerSpecific::StaticGetCommandClassId() ) ); if( cc ) { m_queryPending = cc->RequestState( CommandClass::RequestFlag_Static, 1, Driver::MsgQueue_Query ); addQSC = m_queryPending; } if( !m_queryPending ) { m_queryStage = QueryStage_NodeInfo; m_queryRetries = 0; } } break; } case QueryStage_NodeInfo: { if( !NodeInfoReceived() && m_nodeInfoSupported ) { // obtain from the node a list of command classes that it 1) supports and 2) controls (separated by a mark in the buffer) Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_NodeInfo" ); Msg* msg = new Msg( "Request Node Info", m_nodeId, REQUEST, FUNC_ID_ZW_REQUEST_NODE_INFO, false, true, FUNC_ID_ZW_APPLICATION_UPDATE ); msg->Append( m_nodeId ); GetDriver()->SendMsg( msg, Driver::MsgQueue_Query ); m_queryPending = true; addQSC = true; } else { // This stage has been done already, so move to the Manufacturer Specific stage m_queryStage = QueryStage_SecurityReport; m_queryRetries = 0; } break; } case QueryStage_SecurityReport: { /* For Devices that Support the Security Class, we have to request a list of * Command Classes that Require Security. */ Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_SecurityReport" ); Security* seccc = static_cast( GetCommandClass( Security::StaticGetCommandClassId() ) ); if( seccc ) { // start the process of requesting node state from this sleeping device m_queryPending = seccc->Init(); /* Dont add a Notification Callback here, as this is a multipacket exchange. * the Security Command Class will automatically advance the Query Stage * when we recieve a SecurityCmd_SupportedReport */ addQSC = false; } else { // this is not a Security Device, so move onto the next querystage m_queryStage = QueryStage_ManufacturerSpecific2; m_queryRetries = 0; } break; } case QueryStage_ManufacturerSpecific2: { if( !m_manufacturerSpecificClassReceived ) { // Obtain manufacturer, product type and product ID code from the node device // Manufacturer Specific data is requested before the other command class data so // that we can modify the supported command classes list through the product XML files. Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_ManufacturerSpecific2" ); ManufacturerSpecific* cc = static_cast( GetCommandClass( ManufacturerSpecific::StaticGetCommandClassId() ) ); if( cc ) { m_queryPending = cc->RequestState( CommandClass::RequestFlag_Static, 1, Driver::MsgQueue_Query ); addQSC = m_queryPending; } if( !m_queryPending ) { m_queryStage = QueryStage_Versions; m_queryRetries = 0; } } else { ManufacturerSpecific* cc = static_cast( GetCommandClass( ManufacturerSpecific::StaticGetCommandClassId() ) ); if( cc ) { cc->ReLoadConfigXML(); } m_queryStage = QueryStage_Versions; m_queryRetries = 0; } break; } case QueryStage_Versions: { // Get the version information (if the device supports COMMAND_CLASS_VERSION Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Versions" ); Version* vcc = static_cast( GetCommandClass( Version::StaticGetCommandClassId() ) ); if( vcc ) { for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { CommandClass* cc = it->second; if( cc->GetMaxVersion() > 1 ) { // Get the version for each supported command class that // we have implemented at greater than version one. m_queryPending |= vcc->RequestCommandClassVersion( it->second ); } } addQSC = m_queryPending; } // advance to Instances stage when finished if( !m_queryPending ) { m_queryStage = QueryStage_Instances; m_queryRetries = 0; } break; } case QueryStage_Instances: { // if the device at this node supports multiple instances, obtain a list of these instances Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Instances" ); MultiInstance* micc = static_cast( GetCommandClass( MultiInstance::StaticGetCommandClassId() ) ); if( micc ) { m_queryPending = micc->RequestInstances(); addQSC = m_queryPending; } // when done, advance to the Static stage if( !m_queryPending ) { m_queryStage = QueryStage_Static; m_queryRetries = 0; Log::Write( LogLevel_Info, m_nodeId, "Essential node queries are complete" ); Notification* notification = new Notification( Notification::Type_EssentialNodeQueriesComplete ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); } break; } case QueryStage_Static: { // Request any other static values associated with each command class supported by this node // examples are supported thermostat operating modes, setpoints and fan modes Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Static" ); for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { if( !it->second->IsAfterMark() ) { m_queryPending |= it->second->RequestStateForAllInstances( CommandClass::RequestFlag_Static, Driver::MsgQueue_Query ); } } addQSC = m_queryPending; if( !m_queryPending ) { // when all (if any) static information has been retrieved, advance to the Associations stage m_queryStage = QueryStage_Associations; m_queryRetries = 0; } break; } case QueryStage_Probe1: { Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Probe1" ); // // Send a NoOperation message to see if the node is awake // and alive. Based on the response or lack of response // will determine next step. Called here when configuration exists. // NoOperation* noop = static_cast( GetCommandClass( NoOperation::StaticGetCommandClassId() ) ); if( GetDriver()->GetNodeId() != m_nodeId ) { noop->Set( true ); m_queryPending = true; addQSC = true; } else { m_queryStage = QueryStage_Associations; m_queryRetries = 0; } break; } case QueryStage_Associations: { // if this device supports COMMAND_CLASS_ASSOCIATION, determine to which groups this node belong Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Associations" ); Association* acc = static_cast( GetCommandClass( Association::StaticGetCommandClassId() ) ); if( acc ) { acc->RequestAllGroups( 0 ); m_queryPending = true; addQSC = true; } else { // if this device doesn't support Associations, move to retrieve Session information m_queryStage = QueryStage_Neighbors; m_queryRetries = 0; } break; } case QueryStage_Neighbors: { // retrieves this node's neighbors and stores the neighbor bitmap in the node object Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Neighbors" ); GetDriver()->RequestNodeNeighbors( m_nodeId, 0 ); m_queryPending = true; addQSC = true; break; } case QueryStage_Session: { // Request the session values from the command classes in turn // examples of Session information are: current thermostat setpoints, node names and climate control schedules Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Session" ); for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { if( !it->second->IsAfterMark() ) { m_queryPending |= it->second->RequestStateForAllInstances( CommandClass::RequestFlag_Session, Driver::MsgQueue_Query ); } } addQSC = m_queryPending; if( !m_queryPending ) { m_queryStage = QueryStage_Dynamic; m_queryRetries = 0; } break; } case QueryStage_Dynamic: { // Request the dynamic values from the node, that can change at any time // Examples include on/off state, heating mode, temperature, etc. Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Dynamic" ); m_queryPending = RequestDynamicValues(); addQSC = m_queryPending; if( !m_queryPending ) { m_queryStage = QueryStage_Configuration; m_queryRetries = 0; } break; } case QueryStage_Configuration: { // Request the configurable parameter values from the node. Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Configuration" ); if( m_queryConfiguration ) { if( RequestAllConfigParams( 0 ) ) { m_queryPending = true; addQSC = true; } m_queryConfiguration = false; } if( !m_queryPending ) { m_queryStage = QueryStage_Complete; m_queryRetries = 0; } break; } case QueryStage_Complete: { ClearAddingNode(); // Notify the watchers that the queries are complete for this node Log::Write( LogLevel_Detail, m_nodeId, "QueryStage_Complete" ); Notification* notification = new Notification( Notification::Type_NodeQueriesComplete ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); // Check whether all nodes are now complete GetDriver()->CheckCompletedNodeQueries(); return; } default: { break; } } } if( addQSC && m_nodeAlive ) { // Add a marker to the query queue so this advance method // gets called again once this stage has completed. GetDriver()->SendQueryStageComplete( m_nodeId, m_queryStage ); } } //----------------------------------------------------------------------------- // // We are done with a stage in the query process //----------------------------------------------------------------------------- void Node::QueryStageComplete ( QueryStage const _stage ) { // Check that we are actually on the specified stage if( _stage != m_queryStage ) { return; } if( m_queryStage != QueryStage_Complete ) { // Move to the next stage m_queryPending = false; m_queryStage = (QueryStage)( (uint32)m_queryStage + 1 ); if( m_queryStage == QueryStage_Probe1 ) { m_queryStage = (QueryStage)( (uint32)m_queryStage + 1 ); } m_queryRetries = 0; } } //----------------------------------------------------------------------------- // // Retry a stage up to the specified maximum //----------------------------------------------------------------------------- void Node::QueryStageRetry ( QueryStage const _stage, uint8 const _maxAttempts // = 0 ) { Log::Write( LogLevel_Info, m_nodeId, "QueryStageRetry stage %s requested stage %s max %d retries %d pending %d", c_queryStageNames[_stage], c_queryStageNames[m_queryStage], _maxAttempts, m_queryRetries, m_queryPending); // Check that we are actually on the specified stage if( _stage != m_queryStage ) { return; } m_queryPending = false; if( _maxAttempts && ( ++m_queryRetries >= _maxAttempts ) ) { m_queryRetries = 0; // We've retried too many times. Move to the next stage but only if // we aren't in any of the probe stages. if( m_queryStage != QueryStage_Probe && m_queryStage != QueryStage_Probe1 ) { m_queryStage = (Node::QueryStage)( (uint32)(m_queryStage + 1) ); } } // Repeat the current query stage GetDriver()->RetryQueryStageComplete( m_nodeId, m_queryStage ); } //----------------------------------------------------------------------------- // // Set the query stage (but only to an earlier stage) //----------------------------------------------------------------------------- void Node::SetQueryStage ( QueryStage const _stage, bool const _advance // = true ) { if( (int)_stage < (int)m_queryStage ) { m_queryStage = _stage; m_queryPending = false; if( QueryStage_Configuration == _stage ) { m_queryConfiguration = true; } } if( _advance ) { AdvanceQueries(); } } //----------------------------------------------------------------------------- // // Gets the query stage name //----------------------------------------------------------------------------- string Node::GetQueryStageName ( QueryStage const _stage ) { return c_queryStageNames[_stage]; } //----------------------------------------------------------------------------- // // Gets the neighbors of a node //----------------------------------------------------------------------------- uint32 Node::GetNeighbors ( uint8** o_neighbors ) { // determine how many neighbors there are int i; uint32 numNeighbors = 0; if( m_queryStage < QueryStage_Session ) { *o_neighbors = NULL; return 0; } for( i = 0; i < 29; i++ ) { for( unsigned char mask = 0x80; mask != 0; mask >>= 1 ) if( ( m_neighbors[i] & mask ) != 0 ) numNeighbors++; } // handle the possibility that no neighbors are reported if( !numNeighbors ) { *o_neighbors = NULL; return 0; } // create and populate an array with neighbor node ids uint8* neighbors = new uint8[numNeighbors]; uint32 index = 0; for( int by=0; by<29; by++ ) { for( int bi=0; bi<8; bi++ ) { if( (m_neighbors[by] & ( 0x01< // Read the node config from XML //----------------------------------------------------------------------------- void Node::ReadXML ( TiXmlElement const* _node ) { char const* str; int intVal; str = _node->Attribute( "query_stage" ); if( str ) { // After restoring state from a file, we need to at least refresh the association, session and dynamic values. QueryStage queryStage = QueryStage_Associations; for( uint32 i=0; i<(uint32)QueryStage_Associations; ++i ) { if( !strcmp( str, c_queryStageNames[i] ) ) { queryStage = (QueryStage)i; break; } } SetQueryStage( queryStage, false ); } if( m_queryStage != QueryStage_None ) { if( m_queryStage > QueryStage_ProtocolInfo ) { // Notify the watchers of the protocol info. // We do the notification here so that it gets into the queue ahead of // any other notifications generated by adding command classes etc. m_protocolInfoReceived = true; Notification* notification = new Notification( Notification::Type_NodeProtocolInfo ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); } if( m_queryStage > QueryStage_NodeInfo ) { m_nodeInfoReceived = true; } if( m_queryStage > QueryStage_Instances ) { Notification* notification = new Notification( Notification::Type_EssentialNodeQueriesComplete ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); } } str = _node->Attribute( "name" ); if( str ) { m_nodeName = str; } str = _node->Attribute( "location" ); if( str ) { m_location = str; } if( TIXML_SUCCESS == _node->QueryIntAttribute( "basic", &intVal ) ) { m_basic = (uint8)intVal; } if( TIXML_SUCCESS == _node->QueryIntAttribute( "generic", &intVal ) ) { m_generic = (uint8)intVal; } if( TIXML_SUCCESS == _node->QueryIntAttribute( "specific", &intVal ) ) { m_specific = (uint8)intVal; } str = _node->Attribute( "type" ); if( str ) { m_type = str; } m_listening = true; str = _node->Attribute( "listening" ); if( str ) { m_listening = !strcmp( str, "true" ); } m_frequentListening = false; str = _node->Attribute( "frequentListening" ); if( str ) { m_frequentListening = !strcmp( str, "true" ); } m_beaming = false; str = _node->Attribute( "beaming" ); if( str ) { m_beaming = !strcmp( str, "true" ); } m_routing = true; str = _node->Attribute( "routing" ); if( str ) { m_routing = !strcmp( str, "true" ); } m_maxBaudRate = 0; if( TIXML_SUCCESS == _node->QueryIntAttribute( "max_baud_rate", &intVal ) ) { m_maxBaudRate = (uint32)intVal; } m_version = 0; if( TIXML_SUCCESS == _node->QueryIntAttribute( "version", &intVal ) ) { m_version = (uint8)intVal; } m_security = false; str = _node->Attribute( "security" ); if( str ) { m_security = !strcmp( str, "true" ); } m_nodeInfoSupported = true; str = _node->Attribute( "nodeinfosupported" ); if( str ) { m_nodeInfoSupported = !strcmp( str, "true" ); } // Read the manufacturer info and create the command classes TiXmlElement const* child = _node->FirstChildElement(); while( child ) { str = child->Value(); if( str ) { if( !strcmp( str, "CommandClasses" ) ) { ReadCommandClassesXML( child ); } else if( !strcmp( str, "Manufacturer" ) ) { str = child->Attribute( "id" ); if( str ) { m_manufacturerId = str; } str = child->Attribute( "name" ); if( str ) { m_manufacturerName = str; } TiXmlElement const* product = child->FirstChildElement(); if( !strcmp( product->Value(), "Product" ) ) { str = product->Attribute( "type" ); if( str ) { m_productType = str; } str = product->Attribute( "id" ); if( str ) { m_productId = str; } str = product->Attribute( "name" ); if( str ) { m_productName = str; } } } } // Move to the next child node child = child->NextSiblingElement(); } if( m_nodeName.length() > 0 || m_location.length() > 0 || m_manufacturerId.length() > 0 ) { // Notify the watchers of the name changes Notification* notification = new Notification( Notification::Type_NodeNaming ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); } } //----------------------------------------------------------------------------- // // Read the device's protocol configuration from XML //----------------------------------------------------------------------------- void Node::ReadDeviceProtocolXML ( TiXmlElement const* _ccsElement ) { TiXmlElement const* ccElement = _ccsElement->FirstChildElement(); while( ccElement ) { char const* str = ccElement->Value(); if( str && !strcmp( str, "Protocol" ) ) { str = ccElement->Attribute( "nodeinfosupported" ); if( str ) { m_nodeInfoSupported = !strcmp( str, "true" ); } // Some controllers support API calls that aren't advertised in their returned data. // So provide a way to manipulate the returned data to reflect reality. TiXmlElement const* childElement = _ccsElement->FirstChildElement(); while( childElement ) { str = childElement->Value(); if( str && !strcmp( str, "APIcall" ) ) { char const* funcStr = childElement->Attribute( "function" ); char *p; uint8 func = (uint8)strtol( funcStr, &p, 16 ); if( p != funcStr ) { char const* presStr = ccElement->Attribute( "present" ); GetDriver()->SetAPICall( func, !strcmp( presStr, "true" ) ); } } childElement = childElement->NextSiblingElement(); } return; } ccElement = ccElement->NextSiblingElement(); } } //----------------------------------------------------------------------------- // // Read the command classes from XML //----------------------------------------------------------------------------- void Node::ReadCommandClassesXML ( TiXmlElement const* _ccsElement ) { char const* str; int32 intVal; TiXmlElement const* ccElement = _ccsElement->FirstChildElement(); while( ccElement ) { str = ccElement->Value(); if( str && !strcmp( str, "CommandClass" ) ) { if( TIXML_SUCCESS == ccElement->QueryIntAttribute( "id", &intVal ) ) { uint8 id = (uint8)intVal; // Check whether this command class is to be removed (product XMLs might // request this if a class is not implemented properly by the device) bool remove = false; char const* action = ccElement->Attribute( "action" ); if( action && !strcasecmp( action, "remove" ) ) { remove = true; } CommandClass* cc = GetCommandClass( id ); if( remove ) { // Remove support for the command class RemoveCommandClass( id ); } else { if( NULL == cc ) { // Command class support does not exist yet, so we create it cc = AddCommandClass( id ); } if( NULL != cc ) { cc->ReadXML( ccElement ); } } } } ccElement = ccElement->NextSiblingElement(); } } //----------------------------------------------------------------------------- // // Save the static node configuration data //----------------------------------------------------------------------------- void Node::WriteXML ( TiXmlElement* _driverElement ) { char str[32]; TiXmlElement* nodeElement = new TiXmlElement( "Node" ); _driverElement->LinkEndChild( nodeElement ); snprintf( str, 32, "%d", m_nodeId ); nodeElement->SetAttribute( "id", str ); nodeElement->SetAttribute( "name", m_nodeName.c_str() ); nodeElement->SetAttribute( "location", m_location.c_str() ); snprintf( str, 32, "%d", m_basic ); nodeElement->SetAttribute( "basic", str ); snprintf( str, 32, "%d", m_generic ); nodeElement->SetAttribute( "generic", str ); snprintf( str, 32, "%d", m_specific ); nodeElement->SetAttribute( "specific", str ); nodeElement->SetAttribute( "type", m_type.c_str() ); nodeElement->SetAttribute( "listening", m_listening ? "true" : "false" ); nodeElement->SetAttribute( "frequentListening", m_frequentListening ? "true" : "false" ); nodeElement->SetAttribute( "beaming", m_beaming ? "true" : "false" ); nodeElement->SetAttribute( "routing", m_routing ? "true" : "false" ); snprintf( str, 32, "%d", m_maxBaudRate ); nodeElement->SetAttribute( "max_baud_rate", str ); snprintf( str, 32, "%d", m_version ); nodeElement->SetAttribute( "version", str ); if( m_security ) { nodeElement->SetAttribute( "security", "true" ); } if( !m_nodeInfoSupported ) { nodeElement->SetAttribute( "nodeinfosupported", "false" ); } nodeElement->SetAttribute( "query_stage", c_queryStageNames[m_queryStage] ); // Write the manufacturer and product data in the same format // as used in the ManyfacturerSpecfic.xml file. This will // allow new devices to be added via a simple cut and paste. TiXmlElement* manufacturerElement = new TiXmlElement( "Manufacturer" ); nodeElement->LinkEndChild( manufacturerElement ); manufacturerElement->SetAttribute( "id", m_manufacturerId.c_str() ); manufacturerElement->SetAttribute( "name", m_manufacturerName.c_str() ); TiXmlElement* productElement = new TiXmlElement( "Product" ); manufacturerElement->LinkEndChild( productElement ); productElement->SetAttribute( "type", m_productType.c_str() ); productElement->SetAttribute( "id", m_productId.c_str() ); productElement->SetAttribute( "name", m_productName.c_str() ); // Write the command classes TiXmlElement* ccsElement = new TiXmlElement( "CommandClasses" ); nodeElement->LinkEndChild( ccsElement ); for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { if( it->second->GetCommandClassId() == NoOperation::StaticGetCommandClassId() ) // don't output NoOperation { continue; } TiXmlElement* ccElement = new TiXmlElement( "CommandClass" ); ccsElement->LinkEndChild( ccElement ); it->second->WriteXML( ccElement ); } } //----------------------------------------------------------------------------- // // Handle the FUNC_ID_ZW_GET_NODE_PROTOCOL_INFO response //----------------------------------------------------------------------------- void Node::UpdateProtocolInfo ( uint8 const* _data ) { if( ProtocolInfoReceived() ) { // We already have this info return; } if( _data[4] == 0 ) { // Node doesn't exist if Generic class is zero. Log::Write( LogLevel_Info, m_nodeId, " Protocol Info for Node %d reports node nonexistent", m_nodeId ); SetNodeAlive( false ); return; } // Notify the watchers of the protocol info. // We do the notification here so that it gets into the queue ahead of // any other notifications generated by adding command classes etc. Notification* notification = new Notification( Notification::Type_NodeProtocolInfo ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); // Capabilities m_listening = ( ( _data[0] & 0x80 ) != 0 ); m_routing = ( ( _data[0] & 0x40 ) != 0 ); m_maxBaudRate = 9600; if( ( _data[0] & 0x38 ) == 0x10 ) { m_maxBaudRate = 40000; } m_version = ( _data[0] & 0x07 ) + 1; m_frequentListening = ( ( _data[1] & ( SecurityFlag_Sensor250ms | SecurityFlag_Sensor1000ms ) ) != 0 ); m_beaming = ( ( _data[1] & SecurityFlag_BeamCapability ) != 0 ); // Security m_security = ( ( _data[1] & SecurityFlag_Security ) != 0 ); // Optional flag is true if the device reports optional command classes. // NOTE: We stopped using this because not all devices report it properly, // and now just request the optional classes regardless. // bool optional = (( _data[1] & 0x80 ) != 0 ); Log::Write( LogLevel_Info, m_nodeId, " Protocol Info for Node %d:", m_nodeId ); if( m_listening ) Log::Write( LogLevel_Info, m_nodeId, " Listening = true" ); else { Log::Write( LogLevel_Info, m_nodeId, " Listening = false" ); Log::Write( LogLevel_Info, m_nodeId, " Frequent = %s", m_frequentListening ? "true" : "false" ); } Log::Write( LogLevel_Info, m_nodeId, " Beaming = %s", m_beaming ? "true" : "false" ); Log::Write( LogLevel_Info, m_nodeId, " Routing = %s", m_routing ? "true" : "false" ); Log::Write( LogLevel_Info, m_nodeId, " Max Baud Rate = %d", m_maxBaudRate ); Log::Write( LogLevel_Info, m_nodeId, " Version = %d", m_version ); Log::Write( LogLevel_Info, m_nodeId, " Security = %s", m_security ? "true" : "false" ); // Set up the device class based data for the node, including mandatory command classes SetDeviceClasses( _data[3], _data[4], _data[5] ); // Do this for every controller. A little extra work but it won't be a large file. if( IsController() ) { GetDriver()->ReadButtons( m_nodeId ); } m_protocolInfoReceived = true; } void Node::SetSecuredClasses ( uint8 const* _data, uint8 const _length ) { uint32 i; Log::Write( LogLevel_Info, m_nodeId, " Secured command classes for node %d:", m_nodeId ); bool afterMark = false; for( i=0; i<_length; ++i ) { if( _data[i] == 0xef ) { // COMMAND_CLASS_MARK. // Marks the end of the list of supported command classes. The remaining classes // are those that can be controlled by the device. These classes are created // without values. Messages received cause notification events instead. afterMark = true; continue; } /* Check if this is a CC that is already registered with the node */ if (CommandClass *pCommandClass = GetCommandClass(_data[i])) { if (pCommandClass->IsSecureSupported()) { pCommandClass->SetSecured(); Log::Write( LogLevel_Info, m_nodeId, " %s (Secured)", pCommandClass->GetCommandClassName().c_str() ); } else { Log::Write( LogLevel_Info, m_nodeId, " %s (Downgraded)", pCommandClass->GetCommandClassName().c_str() ); } } /* it might be a new CC we havn't seen as part of the NIF */ else if( CommandClasses::IsSupported( _data[i] ) ) { if( CommandClass* pCommandClass = AddCommandClass( _data[i] ) ) { // If this class came after the COMMAND_CLASS_MARK, then we do not create values. if( afterMark ) { pCommandClass->SetAfterMark(); } if (pCommandClass->IsSecureSupported()) { pCommandClass->SetSecured(); Log::Write( LogLevel_Info, m_nodeId, " %s (Secured)", pCommandClass->GetCommandClassName().c_str() ); } else { Log::Write( LogLevel_Info, m_nodeId, " %s (Downgraded)", pCommandClass->GetCommandClassName().c_str() ); } // Start with an instance count of one. If the device supports COMMMAND_CLASS_MULTI_INSTANCE // then some command class instance counts will increase once the responses to the RequestState // call at the end of this method have been processed. pCommandClass->SetInstance( 1 ); } } else { Log::Write( LogLevel_Info, m_nodeId, " Secure CommandClass 0x%.2x - NOT SUPPORTED", _data[i] ); } } Log::Write( LogLevel_Info, m_nodeId, " UnSecured command classes for node %d:", m_nodeId ); for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { if (!it->second->IsSecured()) Log::Write( LogLevel_Info, m_nodeId, " %s (Unsecured)", it->second->GetCommandClassName().c_str() ); } } //----------------------------------------------------------------------------- // // Set up the command classes from the node info frame //----------------------------------------------------------------------------- void Node::UpdateNodeInfo ( uint8 const* _data, uint8 const _length ) { if( !NodeInfoReceived() ) { // Add the command classes specified by the device Log::Write( LogLevel_Info, m_nodeId, " Optional command classes for node %d:", m_nodeId ); bool newCommandClasses = false; uint32 i; bool afterMark = false; for( i=0; i<_length; ++i ) { if( _data[i] == 0xef ) { // COMMAND_CLASS_MARK. // Marks the end of the list of supported command classes. The remaining classes // are those that can be controlled by the device. These classes are created // without values. Messages received cause notification events instead. afterMark = true; if( !newCommandClasses ) { Log::Write( LogLevel_Info, m_nodeId, " None" ); } Log::Write( LogLevel_Info, m_nodeId, " Optional command classes controlled by node %d:", m_nodeId ); newCommandClasses = false; continue; } if( CommandClasses::IsSupported( _data[i] ) ) { if( CommandClass* pCommandClass = AddCommandClass( _data[i] ) ) { // If this class came after the COMMAND_CLASS_MARK, then we do not create values. if( afterMark ) { pCommandClass->SetAfterMark(); } // Start with an instance count of one. If the device supports COMMMAND_CLASS_MULTI_INSTANCE // then some command class instance counts will increase once the responses to the RequestState // call at the end of this method have been processed. pCommandClass->SetInstance( 1 ); newCommandClasses = true; Log::Write( LogLevel_Info, m_nodeId, " %s", pCommandClass->GetCommandClassName().c_str() ); } } else { Log::Write( LogLevel_Info, m_nodeId, " CommandClass 0x%.2x - NOT REQUIRED", _data[i] ); } } if( !newCommandClasses ) { // No additional command classes over the mandatory ones. Log::Write( LogLevel_Info, m_nodeId, " None" ); } SetStaticRequests(); m_nodeInfoReceived = true; } else { // We probably only need to do the dynamic stuff SetQueryStage( QueryStage_Dynamic ); } // Treat the node info frame as a sign that the node is awake if( WakeUp* wakeUp = static_cast( GetCommandClass( WakeUp::StaticGetCommandClassId() ) ) ) { wakeUp->SetAwake( true ); } } //----------------------------------------------------------------------------- // // Track alive state of a node for dead node detection. //----------------------------------------------------------------------------- void Node::SetNodeAlive ( bool const _isAlive ) { Notification* notification; if( _isAlive ) { Log::Write( LogLevel_Error, m_nodeId, "WARNING: node revived" ); m_nodeAlive = true; m_errors = 0; if( m_queryStage != Node::QueryStage_Complete ) { m_queryRetries = 0; // restart at last stage AdvanceQueries(); } notification = new Notification( Notification::Type_Notification ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); notification->SetNotification( Notification::Code_Alive ); } else { Log::Write( LogLevel_Error, m_nodeId, "ERROR: node presumed dead" ); m_nodeAlive = false; if( m_queryStage != Node::QueryStage_Complete ) { // Check whether all nodes are now complete GetDriver()->CheckCompletedNodeQueries(); } notification = new Notification( Notification::Type_Notification ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); notification->SetNotification( Notification::Code_Dead ); } GetDriver()->QueueNotification( notification ); } //----------------------------------------------------------------------------- // // The first time we hear from a node, we set flags to indicate the // need to request certain static data from the device. This is so that // we can track which data has been received, and which has not. //----------------------------------------------------------------------------- void Node::SetStaticRequests ( ) { uint8 request = 0; if( GetCommandClass( MultiInstance::StaticGetCommandClassId() ) ) { // Request instances request |= (uint8)CommandClass::StaticRequest_Instances; } if( GetCommandClass( Version::StaticGetCommandClassId() ) ) { // Request versions request |= (uint8)CommandClass::StaticRequest_Version; } if( request ) { for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { it->second->SetStaticRequest( request ); } SetQueryStage( QueryStage_ManufacturerSpecific2 ); } } //----------------------------------------------------------------------------- // // Set the name of the node //----------------------------------------------------------------------------- void Node::SetNodeName ( string const& _nodeName ) { m_nodeName = _nodeName; // Notify the watchers of the name changes Notification* notification = new Notification( Notification::Type_NodeNaming ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); if( NodeNaming* cc = static_cast( GetCommandClass( NodeNaming::StaticGetCommandClassId() ) ) ) { // The node supports naming, so we try to write the name into the device cc->SetName( _nodeName ); } } //----------------------------------------------------------------------------- // // Set the location of the node //----------------------------------------------------------------------------- void Node::SetLocation ( string const& _location ) { m_location = _location; // Notify the watchers of the name changes Notification* notification = new Notification( Notification::Type_NodeNaming ); notification->SetHomeAndNodeIds( m_homeId, m_nodeId ); GetDriver()->QueueNotification( notification ); if( NodeNaming* cc = static_cast( GetCommandClass( NodeNaming::StaticGetCommandClassId() ) ) ) { // The node supports naming, so we try to write the location into the device cc->SetLocation( _location ); } } //----------------------------------------------------------------------------- // // Handle a command class message //----------------------------------------------------------------------------- void Node::ApplicationCommandHandler ( uint8 const* _data ) { if( CommandClass* pCommandClass = GetCommandClass( _data[5] ) ) { pCommandClass->ReceivedCntIncr(); pCommandClass->HandleMsg( &_data[6], _data[4] ); } else { if( _data[5] == ControllerReplication::StaticGetCommandClassId() ) { // This is a controller replication message, and we do not support it. // We have to at least acknowledge the message to avoid locking the sending device. Log::Write( LogLevel_Info, m_nodeId, "ApplicationCommandHandler - Default acknowledgement of controller replication data" ); Msg* msg = new Msg( "Replication Command Complete", m_nodeId, REQUEST, FUNC_ID_ZW_REPLICATION_COMMAND_COMPLETE, false ); GetDriver()->SendMsg( msg, Driver::MsgQueue_Command ); } else { Log::Write( LogLevel_Info, m_nodeId, "ApplicationCommandHandler - Unhandled Command Class 0x%.2x", _data[5] ); } } } //----------------------------------------------------------------------------- // // Get the specified command class object if supported, otherwise NULL //----------------------------------------------------------------------------- CommandClass* Node::GetCommandClass ( uint8 const _commandClassId )const { map::const_iterator it = m_commandClassMap.find( _commandClassId ); if( it != m_commandClassMap.end() ) { return it->second; } // Not found return NULL; } //----------------------------------------------------------------------------- // // Add a command class to the node //----------------------------------------------------------------------------- CommandClass* Node::AddCommandClass ( uint8 const _commandClassId ) { if( GetCommandClass( _commandClassId ) ) { // Class and instance have already been added return NULL; } // Create the command class object and add it to our map if( CommandClass* pCommandClass = CommandClasses::CreateCommandClass( _commandClassId, m_homeId, m_nodeId ) ) { m_commandClassMap[_commandClassId] = pCommandClass; return pCommandClass; } else { Log::Write( LogLevel_Info, m_nodeId, "AddCommandClass - Unsupported Command Class 0x%.2x", _commandClassId ); } return NULL; } //----------------------------------------------------------------------------- // // Remove a command class from the node //----------------------------------------------------------------------------- void Node::RemoveCommandClass ( uint8 const _commandClassId ) { map::iterator it = m_commandClassMap.find( _commandClassId ); if( it == m_commandClassMap.end() ) { // Class is not found return; } // Remove all the values associated with this class if( ValueStore* store = GetValueStore() ) { store->RemoveCommandClassValues( _commandClassId ); } // Destroy the command class object and remove it from our map Log::Write( LogLevel_Info, m_nodeId, "RemoveCommandClass - Removed support for %s", it->second->GetCommandClassName().c_str() ); delete it->second; m_commandClassMap.erase( it ); } //----------------------------------------------------------------------------- // // Set a configuration parameter in a device //----------------------------------------------------------------------------- bool Node::SetConfigParam ( uint8 const _param, int32 _value, uint8 const _size ) { if( Configuration* cc = static_cast( GetCommandClass( Configuration::StaticGetCommandClassId() ) ) ) { // First try to find an existing value representing the parameter, and set that. if( Value* value = cc->GetValue( 1, _param ) ) { switch( value->GetID().GetType() ) { case ValueID::ValueType_Bool: { ValueBool* valueBool = static_cast( value ); valueBool->Set( _value != 0 ); break; } case ValueID::ValueType_Byte: { ValueByte* valueByte = static_cast( value ); valueByte->Set( (uint8)_value ); break; } case ValueID::ValueType_Short: { ValueShort* valueShort = static_cast( value ); valueShort->Set( (uint16)_value ); break; } case ValueID::ValueType_Int: { ValueInt* valueInt = static_cast( value ); valueInt->Set( _value ); break; } case ValueID::ValueType_List: { ValueList* valueList = static_cast( value ); valueList->SetByValue( _value ); break; } default: { } } return true; } // Failed to find an existing value object representing this // configuration parameter, so we try using the default or // included size through the Configuration command class. cc->Set( _param, _value, _size ); return true; } return false; } //----------------------------------------------------------------------------- // // Request the value of a configuration parameter from the device //----------------------------------------------------------------------------- void Node::RequestConfigParam ( uint8 const _param ) { if( Configuration* cc = static_cast( GetCommandClass( Configuration::StaticGetCommandClassId() ) ) ) { cc->RequestValue( 0, _param, 1, Driver::MsgQueue_Send ); } } //----------------------------------------------------------------------------- // // Request the values of all known configuration parameters from the device //----------------------------------------------------------------------------- bool Node::RequestAllConfigParams ( uint32 const _requestFlags ) { bool res = false; if( Configuration* cc = static_cast( GetCommandClass( Configuration::StaticGetCommandClassId() ) ) ) { // Go through all the values in the value store, and request all those which are in the Configuration command class for( ValueStore::Iterator it = m_values->Begin(); it != m_values->End(); ++it ) { Value* value = it->second; if( value->GetID().GetCommandClassId() == Configuration::StaticGetCommandClassId() && !value->IsWriteOnly() ) { res |= cc->RequestValue( _requestFlags, value->GetID().GetIndex(), 1, Driver::MsgQueue_Send ); } } } return res; } //----------------------------------------------------------------------------- // // Request an update of all known dynamic values from the device //----------------------------------------------------------------------------- bool Node::RequestDynamicValues ( ) { bool res = false; for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { if( !it->second->IsAfterMark() ) { res |= it->second->RequestStateForAllInstances( CommandClass::RequestFlag_Dynamic, Driver::MsgQueue_Send ); } } return res; } //----------------------------------------------------------------------------- // // Helper method to set a device's basic level //----------------------------------------------------------------------------- void Node::SetLevel ( uint8 const _level ) { // Level is 0-99, with 0 = off and 99 = fully on. 255 = turn on at last level. uint8 adjustedLevel = _level; if( ( _level > 99 ) && ( _level < 255 ) ) { adjustedLevel = 99; } if( Basic* cc = static_cast( GetCommandClass( Basic::StaticGetCommandClassId() ) ) ) { cc->Set( adjustedLevel ); } } //----------------------------------------------------------------------------- // // Helper method to set a device to be on //----------------------------------------------------------------------------- void Node::SetNodeOn ( ) { // Level is 0-99, with 0 = off and 99 = fully on. 255 = turn on at last level. if( Basic* cc = static_cast( GetCommandClass( Basic::StaticGetCommandClassId() ) ) ) { cc->Set( 255 ); } } //----------------------------------------------------------------------------- // // Helper method to set a device to be off //----------------------------------------------------------------------------- void Node::SetNodeOff ( ) { // Level is 0-99, with 0 = off and 99 = fully on. 255 = turn on at last level. if( Basic* cc = static_cast( GetCommandClass( Basic::StaticGetCommandClassId() ) ) ) { cc->Set( 0 ); } } //----------------------------------------------------------------------------- // // Helper to create a ValueID //----------------------------------------------------------------------------- ValueID Node::CreateValueID ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, ValueID::ValueType const _type ) { return ValueID( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _type ); } //----------------------------------------------------------------------------- // // Helper to create a new bool value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueBool ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, bool const _default, uint8 const _pollIntensity ) { ValueBool* value = new ValueBool( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _default, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new trigger value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueButton ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, uint8 const _pollIntensity ) { ValueButton* value = new ValueButton( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new byte value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueByte ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, uint8 const _default, uint8 const _pollIntensity ) { ValueByte* value = new ValueByte( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _default, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new decimal value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueDecimal ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, string const& _default, uint8 const _pollIntensity ) { ValueDecimal* value = new ValueDecimal( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _default, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new int value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueInt ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, int32 const _default, uint8 const _pollIntensity ) { ValueInt* value = new ValueInt( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _default, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new list value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueList ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, uint8 const _size, vector const& _items, int32 const _default, uint8 const _pollIntensity ) { ValueList* value = new ValueList( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _items, _default, _pollIntensity, _size ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new raw value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueRaw ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, uint8 const* _default, uint8 const _length, uint8 const _pollIntensity ) { ValueRaw* value = new ValueRaw( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _default, _length, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new schedule value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueSchedule ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, uint8 const _pollIntensity ) { ValueSchedule* value = new ValueSchedule( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new short value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueShort ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, int16 const _default, uint8 const _pollIntensity ) { ValueShort* value = new ValueShort( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _default, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to create a new string value and add it to the value store //----------------------------------------------------------------------------- bool Node::CreateValueString ( ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, string const& _default, uint8 const _pollIntensity ) { ValueString* value = new ValueString( m_homeId, m_nodeId, _genre, _commandClassId, _instance, _valueIndex, _label, _units, _readOnly, _writeOnly, _default, _pollIntensity ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); return false; } //----------------------------------------------------------------------------- // // Helper to remove an existing list value from the value store //----------------------------------------------------------------------------- void Node::RemoveValueList ( ValueList* _value ) { ValueStore* store = GetValueStore(); store->RemoveValue( _value->GetID().GetValueStoreKey() ); } //----------------------------------------------------------------------------- // // Get the value object with the specified ID //----------------------------------------------------------------------------- bool Node::CreateValueFromXML ( uint8 const _commandClassId, TiXmlElement const* _valueElement ) { Value* value = NULL; // Create the value ValueID::ValueType type = Value::GetTypeEnumFromName( _valueElement->Attribute( "type" ) ); switch( type ) { case ValueID::ValueType_Bool: { value = new ValueBool(); break; } case ValueID::ValueType_Byte: { value = new ValueByte(); break; } case ValueID::ValueType_Decimal: { value = new ValueDecimal(); break; } case ValueID::ValueType_Int: { value = new ValueInt(); break; } case ValueID::ValueType_List: { value = new ValueList(); break; } case ValueID::ValueType_Schedule: { value = new ValueSchedule(); break; } case ValueID::ValueType_Short: { value = new ValueShort(); break; } case ValueID::ValueType_String: { value = new ValueString(); break; } case ValueID::ValueType_Button: { value = new ValueButton(); break; } case ValueID::ValueType_Raw: { value = new ValueRaw(); break; } default: { Log::Write( LogLevel_Info, m_nodeId, "Unknown ValueType in XML: %s", _valueElement->Attribute( "type" ) ); break; } } if( value ) { value->ReadXML( m_homeId, m_nodeId, _commandClassId, _valueElement ); ValueStore* store = GetValueStore(); if( store->AddValue( value ) ) { value->Release(); return true; } value->Release(); } return false; } //----------------------------------------------------------------------------- // // Apply XML differences to a value //----------------------------------------------------------------------------- void Node::ReadValueFromXML ( uint8 const _commandClassId, TiXmlElement const* _valueElement ) { int32 intVal; ValueID::ValueGenre genre = Value::GetGenreEnumFromName( _valueElement->Attribute( "genre" ) ); ValueID::ValueType type = Value::GetTypeEnumFromName( _valueElement->Attribute( "type" ) ); uint8 instance = 0; if( TIXML_SUCCESS == _valueElement->QueryIntAttribute( "instance", &intVal ) ) { instance = (uint8)intVal; } uint8 index = 0; if( TIXML_SUCCESS == _valueElement->QueryIntAttribute( "index", &intVal ) ) { index = (uint8)intVal; } ValueID id = ValueID( m_homeId, m_nodeId, genre, _commandClassId, instance, index, type ); // Try to get the value from the ValueStore (everything except configuration parameters // should already have been created when the command class instance count was read in). // Create it if it doesn't already exist. if( ValueStore* store = GetValueStore() ) { if( Value* value = store->GetValue( id.GetValueStoreKey() ) ) { value->ReadXML( m_homeId, m_nodeId, _commandClassId, _valueElement ); value->Release(); } else { CreateValueFromXML( _commandClassId, _valueElement ); } } } //----------------------------------------------------------------------------- // // Get the value object with the specified ID //----------------------------------------------------------------------------- Value* Node::GetValue ( ValueID const& _id ) { // This increments the value's reference count return GetValueStore()->GetValue( _id.GetValueStoreKey() ); } //----------------------------------------------------------------------------- // // Get the value object with the specified settings //----------------------------------------------------------------------------- Value* Node::GetValue ( uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex ) { Value* value = NULL; ValueStore* store = GetValueStore(); // This increments the value's reference count value = store->GetValue( ValueID::GetValueStoreKey( _commandClassId, _instance, _valueIndex ) ); return value; } //----------------------------------------------------------------------------- // // Remove the value object with the specified settings //----------------------------------------------------------------------------- bool Node::RemoveValue ( uint8 const _commandClassId, uint8 const _instance, uint8 const _valueIndex ) { ValueStore* store = GetValueStore(); return store->RemoveValue( ValueID::GetValueStoreKey( _commandClassId, _instance, _valueIndex ) ); } //----------------------------------------------------------------------------- // // Get a Group from the node's map //----------------------------------------------------------------------------- Group* Node::GetGroup ( uint8 const _groupIdx ) { map::iterator it = m_groups.find( _groupIdx ); if( it == m_groups.end() ) { return NULL; } return it->second; } //----------------------------------------------------------------------------- // // Add a group into the node's map //----------------------------------------------------------------------------- void Node::AddGroup ( Group* _group ) { map::iterator it = m_groups.find( _group->GetIdx() ); if( it != m_groups.end() ) { // There is already a group with this id. We will replace it. delete it->second; m_groups.erase( it ); } m_groups[_group->GetIdx()] = _group; } //----------------------------------------------------------------------------- // // Save the group data //----------------------------------------------------------------------------- void Node::WriteGroups ( TiXmlElement* _associationsElement ) { for( map::iterator it = m_groups.begin(); it != m_groups.end(); ++it ) { Group* group = it->second; TiXmlElement* groupElement = new TiXmlElement( "Group" ); _associationsElement->LinkEndChild( groupElement ); group->WriteXML( groupElement ); } } //----------------------------------------------------------------------------- // // Gets the number of association groups reported by this node //----------------------------------------------------------------------------- uint8 Node::GetNumGroups ( ) { return (uint8) m_groups.size(); } //----------------------------------------------------------------------------- // // Gets the associations for a group //----------------------------------------------------------------------------- uint32 Node::GetAssociations ( uint8 const _groupIdx, uint8** o_associations ) { uint32 numAssociations = 0; if( Group* group = GetGroup( _groupIdx ) ) { numAssociations = group->GetAssociations( o_associations ); } return numAssociations; } //----------------------------------------------------------------------------- // // Gets the maximum number of associations for a group //----------------------------------------------------------------------------- uint8 Node::GetMaxAssociations ( uint8 const _groupIdx ) { uint8 maxAssociations = 0; if( Group* group = GetGroup( _groupIdx ) ) { maxAssociations = group->GetMaxAssociations(); } return maxAssociations; } //----------------------------------------------------------------------------- // // Gets the label for a particular group //----------------------------------------------------------------------------- string Node::GetGroupLabel ( uint8 const _groupIdx ) { string label = ""; if( Group* group = GetGroup( _groupIdx ) ) { label = group->GetLabel(); } return label; } //----------------------------------------------------------------------------- // // Adds a node to an association group //----------------------------------------------------------------------------- void Node::AddAssociation ( uint8 const _groupIdx, uint8 const _targetNodeId ) { if( Group* group = GetGroup( _groupIdx ) ) { group->AddAssociation( _targetNodeId ); } } //----------------------------------------------------------------------------- // // Removes a node from an association group //----------------------------------------------------------------------------- void Node::RemoveAssociation ( uint8 const _groupIdx, uint8 const _targetNodeId ) { if( Group* group = GetGroup( _groupIdx ) ) { group->RemoveAssociation( _targetNodeId ); } } //----------------------------------------------------------------------------- // // Automatically associate the controller with certain groups //----------------------------------------------------------------------------- void Node::AutoAssociate ( ) { bool autoAssociate = false; Options::Get()->GetOptionAsBool( "Associate", &autoAssociate ); if( autoAssociate ) { // Try to automatically associate with any groups that have been flagged. uint8 controllerNodeId = GetDriver()->GetNodeId(); for( map::iterator it = m_groups.begin(); it != m_groups.end(); ++it ) { Group* group = it->second; if( group->IsAuto() && !group->Contains( controllerNodeId ) ) { // Associate the controller into the group Log::Write( LogLevel_Info, m_nodeId, "Adding the controller to group %d (%s) of node %d", group->GetIdx(), group->GetLabel().c_str(), GetNodeId() ); group->AddAssociation( controllerNodeId ); } } } } //----------------------------------------------------------------------------- // // Get a pointer to our driver //----------------------------------------------------------------------------- Driver* Node::GetDriver ( )const { return( Manager::Get()->GetDriver( m_homeId ) ); } //----------------------------------------------------------------------------- // Device Classes //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // // Use the device class data to get a label for a MultiChannel EndPoint. //----------------------------------------------------------------------------- string Node::GetEndPointDeviceClassLabel ( uint8 const _generic, uint8 const _specific ) { char str[32]; string label; snprintf( str, sizeof(str), "Generic 0x%.2x Specific 0x%.2x", _generic, _specific ); label = str; // Read in the device class data if it has not been read already. if( !s_deviceClassesLoaded ) { ReadDeviceClasses(); } // Get the Generic device class label map::iterator git = s_genericDeviceClasses.find( _generic ); if( git != s_genericDeviceClasses.end() ) { GenericDeviceClass* genericDeviceClass = git->second; label = genericDeviceClass->GetLabel(); // Override with any specific device class label if( DeviceClass* specificDeviceClass = genericDeviceClass->GetSpecificDeviceClass( _specific ) ) { label = specificDeviceClass->GetLabel(); } } return label; } //----------------------------------------------------------------------------- // // Set the device class data for the node //----------------------------------------------------------------------------- bool Node::SetDeviceClasses ( uint8 const _basic, uint8 const _generic, uint8 const _specific ) { m_basic = _basic; m_generic = _generic; m_specific = _specific; // Read in the device class data if it has not been read already. if( !s_deviceClassesLoaded ) { ReadDeviceClasses(); } // Get the basic device class label map::iterator bit = s_basicDeviceClasses.find( _basic ); if( bit != s_basicDeviceClasses.end() ) { m_type = bit->second; Log::Write( LogLevel_Info, m_nodeId, " Basic device class (0x%.2x) - %s", m_basic, m_type.c_str() ); } else { Log::Write( LogLevel_Info, m_nodeId, " Basic device class unknown" ); } // Apply any Generic device class data uint8 basicMapping = 0; map::iterator git = s_genericDeviceClasses.find( _generic ); if( git != s_genericDeviceClasses.end() ) { GenericDeviceClass* genericDeviceClass = git->second; m_type = genericDeviceClass->GetLabel(); Log::Write( LogLevel_Info, m_nodeId, " Generic device Class (0x%.2x) - %s", m_generic, m_type.c_str() ); // Add the mandatory command classes for this generic class type AddMandatoryCommandClasses( genericDeviceClass->GetMandatoryCommandClasses() ); // Get the command class that COMMAND_CLASS_BASIC maps to. basicMapping = genericDeviceClass->GetBasicMapping(); // Apply any Specific device class data if( DeviceClass* specificDeviceClass = genericDeviceClass->GetSpecificDeviceClass( _specific ) ) { m_type = specificDeviceClass->GetLabel(); Log::Write( LogLevel_Info, m_nodeId, " Specific device class (0x%.2x) - %s", m_specific, m_type.c_str() ); // Add the mandatory command classes for this specific class type AddMandatoryCommandClasses( specificDeviceClass->GetMandatoryCommandClasses() ); if( specificDeviceClass->GetBasicMapping() ) { // Override the generic device class basic mapping with the specific device class one. basicMapping = specificDeviceClass->GetBasicMapping(); } } else { Log::Write( LogLevel_Info, m_nodeId, " No specific device class defined" ); } } else { Log::Write( LogLevel_Info, m_nodeId, " No generic or specific device classes defined" ); } // Deal with sleeping devices if( !m_listening && !IsFrequentListeningDevice()) { // Device does not always listen, so we need the WakeUp handler. We can't // wait for the command class list because the request for the command // classes may need to go in the wakeup queue itself! if( CommandClass* pCommandClass = AddCommandClass( WakeUp::StaticGetCommandClassId() ) ) { pCommandClass->SetInstance( 1 ); } } // Apply any COMMAND_CLASS_BASIC remapping if( Basic* cc = static_cast( GetCommandClass( Basic::StaticGetCommandClassId() ) ) ) { cc->SetMapping( basicMapping ); } // Write the mandatory command classes to the log if( !m_commandClassMap.empty() ) { map::const_iterator cit; Log::Write( LogLevel_Info, m_nodeId, " Mandatory Command Classes for Node %d:", m_nodeId ); bool reportedClasses = false; for( cit = m_commandClassMap.begin(); cit != m_commandClassMap.end(); ++cit ) { if( !cit->second->IsAfterMark() && cit->second->GetCommandClassId() != NoOperation::StaticGetCommandClassId() ) { Log::Write( LogLevel_Info, m_nodeId, " %s", cit->second->GetCommandClassName().c_str() ); reportedClasses = true; } } if( !reportedClasses ) { Log::Write( LogLevel_Info, m_nodeId, " None" ); } Log::Write( LogLevel_Info, m_nodeId, " Mandatory Command Classes controlled by Node %d:", m_nodeId ); reportedClasses = false; for( cit = m_commandClassMap.begin(); cit != m_commandClassMap.end(); ++cit ) { if( cit->second->IsAfterMark() ) { Log::Write( LogLevel_Info, m_nodeId, " %s", cit->second->GetCommandClassName().c_str() ); reportedClasses = true; } } if( !reportedClasses ) { Log::Write( LogLevel_Info, m_nodeId, " None" ); } } return true; } //----------------------------------------------------------------------------- // // Add mandatory command classes to the node //----------------------------------------------------------------------------- bool Node::AddMandatoryCommandClasses ( uint8 const* _commandClasses ) { if( NULL == _commandClasses ) { // No command classes to add return false; } int i=0; bool afterMark = false; while( uint8 cc = _commandClasses[i++] ) { if( cc == 0xef ) { // COMMAND_CLASS_MARK. // Marks the end of the list of supported command classes. The remaining classes // are those that can be controlled by this device, which we can ignore. afterMark = true; continue; } if( CommandClasses::IsSupported( cc ) ) { if( CommandClass* commandClass = AddCommandClass( cc ) ) { // If this class came after the COMMAND_CLASS_MARK, then we do not create values. if( afterMark ) { commandClass->SetAfterMark(); } // Start with an instance count of one. If the device supports COMMMAND_CLASS_MULTI_INSTANCE // then some command class instance counts will increase. commandClass->SetInstance( 1 ); } } } return true; } //----------------------------------------------------------------------------- // // Read the static device class data from the device_classes.xml file //----------------------------------------------------------------------------- void Node::ReadDeviceClasses ( ) { // Load the XML document that contains the device class information string configPath; Options::Get()->GetOptionAsString( "ConfigPath", &configPath ); string filename = configPath + string("device_classes.xml"); TiXmlDocument doc; if( !doc.LoadFile( filename.c_str(), TIXML_ENCODING_UTF8 ) ) { Log::Write( LogLevel_Info, "Failed to load device_classes.xml" ); Log::Write( LogLevel_Info, "Check that the config path provided when creating the Manager points to the correct location." ); return; } TiXmlElement const* deviceClassesElement = doc.RootElement(); // Read the basic and generic device classes TiXmlElement const* child = deviceClassesElement->FirstChildElement(); while( child ) { char const* str = child->Value(); if( str ) { char const* keyStr = child->Attribute( "key" ); if( keyStr ) { char* pStop; uint8 key = (uint8)strtol( keyStr, &pStop, 16 ); if( !strcmp( str, "Generic" ) ) { s_genericDeviceClasses[key] = new GenericDeviceClass( child ); } else if( !strcmp( str, "Basic" ) ) { char const* label = child->Attribute( "label" ); if( label ) { s_basicDeviceClasses[key] = label; } } } } child = child->NextSiblingElement(); } s_deviceClassesLoaded = true; } //----------------------------------------------------------------------------- // // Return driver statistics //----------------------------------------------------------------------------- void Node::GetNodeStatistics ( NodeData* _data ) { _data->m_sentCnt = m_sentCnt; _data->m_sentFailed = m_sentFailed; _data->m_retries = m_retries; _data->m_receivedCnt = m_receivedCnt; _data->m_receivedDups = m_receivedDups; _data->m_receivedUnsolicited = m_receivedUnsolicited; _data->m_lastRequestRTT = m_lastRequestRTT; _data->m_lastResponseRTT = m_lastResponseRTT; _data->m_sentTS = m_sentTS.GetAsString(); _data->m_receivedTS = m_receivedTS.GetAsString(); _data->m_averageRequestRTT = m_averageRequestRTT; _data->m_averageResponseRTT = m_averageResponseRTT; _data->m_quality = m_quality; memcpy( _data->m_lastReceivedMessage, m_lastReceivedMessage, sizeof(m_lastReceivedMessage) ); for( map::const_iterator it = m_commandClassMap.begin(); it != m_commandClassMap.end(); ++it ) { CommandClassData ccData; ccData.m_commandClassId = it->second->GetCommandClassId(); ccData.m_sentCnt = it->second->GetSentCnt(); ccData.m_receivedCnt = it->second->GetReceivedCnt(); _data->m_ccData.push_back( ccData ); } } //----------------------------------------------------------------------------- // // Constructor //----------------------------------------------------------------------------- Node::DeviceClass::DeviceClass ( TiXmlElement const* _el ): m_mandatoryCommandClasses(NULL), m_basicMapping(0) { char const* str = _el->Attribute( "label" ); if( str ) { m_label = str; } str = _el->Attribute( "command_classes" ); if( str ) { // Parse the comma delimted command class // list into a temporary vector. vector ccs; char* pos = const_cast(str); while( *pos ) { ccs.push_back( (uint8)strtol( pos, &pos, 16 ) ); if( (*pos) == ',' ) { ++pos; } } // Copy the vector contents into an array. size_t numCCs = ccs.size(); m_mandatoryCommandClasses = new uint8[numCCs+1]; m_mandatoryCommandClasses[numCCs] = 0; // Zero terminator for( uint32 i=0; iAttribute( "basic" ); if( str ) { char* pStop; m_basicMapping = (uint8)strtol( str, &pStop, 16 ); } } //----------------------------------------------------------------------------- // // Constructor //----------------------------------------------------------------------------- Node::GenericDeviceClass::GenericDeviceClass ( TiXmlElement const* _el ): DeviceClass( _el ) { // Add any specific device classes TiXmlElement const* child = _el->FirstChildElement(); while( child ) { char const* str = child->Value(); if( str && !strcmp( str, "Specific" ) ) { char const* keyStr = child->Attribute( "key" ); if( keyStr ) { char* pStop; uint8 key = (uint8)strtol( keyStr, &pStop, 16 ); m_specificDeviceClasses[key] = new DeviceClass( child ); } } child = child->NextSiblingElement(); } } //----------------------------------------------------------------------------- // // Destructor //----------------------------------------------------------------------------- Node::GenericDeviceClass::~GenericDeviceClass ( ) { while( !m_specificDeviceClasses.empty() ) { map::iterator it = m_specificDeviceClasses.begin(); delete it->second; m_specificDeviceClasses.erase( it ); } } //----------------------------------------------------------------------------- // // Get a specific device class object //----------------------------------------------------------------------------- Node::DeviceClass* Node::GenericDeviceClass::GetSpecificDeviceClass ( uint8 const& _specific ) { map::iterator it = m_specificDeviceClasses.find( _specific ); if( it != m_specificDeviceClasses.end() ) { return it->second; } return NULL; }