//----------------------------------------------------------------------------- // // ManufacturerSpecific.cpp // // Implementation of the Z-Wave COMMAND_CLASS_MANUFACTURER_SPECIFIC // // Copyright (c) 2010 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 "CommandClasses.h" #include "ManufacturerSpecific.h" #include "../tinyxml.h" #include "../Defs.h" #include "../Msg.h" #include "../Node.h" #include "../Options.h" #include "../Manager.h" #include "../Driver.h" #include "../Notification.h" #include "../platform/Log.h" #include "../value_classes/ValueStore.h" #include "../value_classes/ValueString.h" using namespace OpenZWave; enum ManufacturerSpecificCmd { ManufacturerSpecificCmd_Get = 0x04, ManufacturerSpecificCmd_Report = 0x05 }; map ManufacturerSpecific::s_manufacturerMap; map ManufacturerSpecific::s_productMap; bool ManufacturerSpecific::s_bXmlLoaded = false; //----------------------------------------------------------------------------- // // Request current state from the device //----------------------------------------------------------------------------- bool ManufacturerSpecific::RequestState ( uint32 const _requestFlags, uint8 const _instance, Driver::MsgQueue const _queue ) { if( ( _requestFlags & RequestFlag_Static ) && HasStaticRequest( StaticRequest_Values ) ) { return RequestValue( _requestFlags, 0, _instance, _queue ); } return false; } //----------------------------------------------------------------------------- // // Request current value from the device //----------------------------------------------------------------------------- bool ManufacturerSpecific::RequestValue ( uint32 const _requestFlags, uint8 const _dummy1, // = 0 (not used) uint8 const _instance, Driver::MsgQueue const _queue ) { if( _instance != 1 ) { // This command class doesn't work with multiple instances return false; } if ( IsGetSupported() ) { Msg* msg = new Msg( "ManufacturerSpecificCmd_Get", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() ); msg->Append( GetNodeId() ); msg->Append( 2 ); msg->Append( GetCommandClassId() ); msg->Append( ManufacturerSpecificCmd_Get ); msg->Append( GetDriver()->GetTransmitOptions() ); GetDriver()->SendMsg( msg, _queue ); return true; } else { Log::Write( LogLevel_Info, GetNodeId(), "ManufacturerSpecificCmd_Get Not Supported on this node"); } return false; } string ManufacturerSpecific::SetProductDetails ( Node* node, uint16 manufacturerId, uint16 productType, uint16 productId ) { char str[64]; if (!s_bXmlLoaded) LoadProductXML(); snprintf( str, sizeof(str), "Unknown: id=%.4x", manufacturerId ); string manufacturerName = str; snprintf( str, sizeof(str), "Unknown: type=%.4x, id=%.4x", productType, productId ); string productName = str; string configPath = ""; // Try to get the real manufacturer and product names map::iterator mit = s_manufacturerMap.find( manufacturerId ); if( mit != s_manufacturerMap.end() ) { // Replace the id with the real name manufacturerName = mit->second; // Get the product map::iterator pit = s_productMap.find( Product::GetKey( manufacturerId, productType, productId ) ); if( pit != s_productMap.end() ) { productName = pit->second->GetProductName(); configPath = pit->second->GetConfigPath(); } } // Set the values into the node // Only set the manufacturer and product name if they are // empty - we don't want to overwrite any user defined names. if( node->GetManufacturerName() == "" ) { node->SetManufacturerName( manufacturerName ); } if( node->GetProductName() == "" ) { node->SetProductName( productName ); } snprintf( str, sizeof(str), "%.4x", manufacturerId ); node->SetManufacturerId( str ); snprintf( str, sizeof(str), "%.4x", productType ); node->SetProductType( str ); snprintf( str, sizeof(str), "%.4x", productId ); node->SetProductId( str ); return configPath; } //----------------------------------------------------------------------------- // // Handle a message from the Z-Wave network //----------------------------------------------------------------------------- bool ManufacturerSpecific::HandleMsg ( uint8 const* _data, uint32 const _length, uint32 const _instance // = 1 ) { if( ManufacturerSpecificCmd_Report == (ManufacturerSpecificCmd)_data[0] ) { // first two bytes are manufacturer id code uint16 manufacturerId = (((uint16)_data[1])<<8) | (uint16)_data[2]; // next four are product type and product id uint16 productType = (((uint16)_data[3])<<8) | (uint16)_data[4]; uint16 productId = (((uint16)_data[5])<<8) | (uint16)_data[6]; if( Node* node = GetNodeUnsafe() ) { // Attempt to create the config parameters string configPath = SetProductDetails( node, manufacturerId, productType, productId); if( configPath.size() > 0 ) { LoadConfigXML( node, configPath ); } Log::Write( LogLevel_Info, GetNodeId(), "Received manufacturer specific report from node %d: Manufacturer=%s, Product=%s", GetNodeId(), node->GetManufacturerName().c_str(), node->GetProductName().c_str() ); ClearStaticRequest( StaticRequest_Values ); node->m_manufacturerSpecificClassReceived = true; } // Notify the watchers of the name changes Notification* notification = new Notification( Notification::Type_NodeNaming ); notification->SetHomeAndNodeIds( GetHomeId(), GetNodeId() ); GetDriver()->QueueNotification( notification ); return true; } return false; } //----------------------------------------------------------------------------- // // Load the XML that maps manufacturer and product IDs to human-readable names //----------------------------------------------------------------------------- bool ManufacturerSpecific::LoadProductXML ( ) { s_bXmlLoaded = true; // Parse the Z-Wave manufacturer and product XML file. string configPath; Options::Get()->GetOptionAsString( "ConfigPath", &configPath ); string filename = configPath + "manufacturer_specific.xml"; TiXmlDocument* pDoc = new TiXmlDocument(); if( !pDoc->LoadFile( filename.c_str(), TIXML_ENCODING_UTF8 ) ) { delete pDoc; Log::Write( LogLevel_Info, "Unable to load %s", filename.c_str() ); return false; } TiXmlElement const* root = pDoc->RootElement(); char const* str; char* pStopChar; TiXmlElement const* manufacturerElement = root->FirstChildElement(); while( manufacturerElement ) { str = manufacturerElement->Value(); if( str && !strcmp( str, "Manufacturer" ) ) { // Read in the manufacturer attributes str = manufacturerElement->Attribute( "id" ); if( !str ) { Log::Write( LogLevel_Info, "Error in manufacturer_specific.xml at line %d - missing manufacturer id attribute", manufacturerElement->Row() ); delete pDoc; return false; } uint16 manufacturerId = (uint16)strtol( str, &pStopChar, 16 ); str = manufacturerElement->Attribute( "name" ); if( !str ) { Log::Write( LogLevel_Info, "Error in manufacturer_specific.xml at line %d - missing manufacturer name attribute", manufacturerElement->Row() ); delete pDoc; return false; } // Add this manufacturer to the map s_manufacturerMap[manufacturerId] = str; // Parse all the products for this manufacturer TiXmlElement const* productElement = manufacturerElement->FirstChildElement(); while( productElement ) { str = productElement->Value(); if( str && !strcmp( str, "Product" ) ) { str = productElement->Attribute( "type" ); if( !str ) { Log::Write( LogLevel_Info, "Error in manufacturer_specific.xml at line %d - missing product type attribute", productElement->Row() ); delete pDoc; return false; } uint16 productType = (uint16)strtol( str, &pStopChar, 16 ); str = productElement->Attribute( "id" ); if( !str ) { Log::Write( LogLevel_Info, "Error in manufacturer_specific.xml at line %d - missing product id attribute", productElement->Row() ); delete pDoc; return false; } uint16 productId = (uint16)strtol( str, &pStopChar, 16 ); str = productElement->Attribute( "name" ); if( !str ) { Log::Write( LogLevel_Info, "Error in manufacturer_specific.xml at line %d - missing product name attribute", productElement->Row() ); delete pDoc; return false; } string productName = str; // Optional config path string configPath; str = productElement->Attribute( "config" ); if( str ) { configPath = str; } // Add the product to the map Product* product = new Product( manufacturerId, productType, productId, productName, configPath ); if ( s_productMap[product->GetKey()] != NULL ) { Product *c = s_productMap[product->GetKey()]; Log::Write( LogLevel_Info, "Product name collision: %s type %x id %x manufacturerid %x, collides with %s, type %x id %x manufacturerid %x", productName.c_str(), productType, productId, manufacturerId, c->GetProductName().c_str(), c->GetProductType(), c->GetProductId(), c->GetManufacturerId()); delete product; } else { s_productMap[product->GetKey()] = product; } } // Move on to the next product. productElement = productElement->NextSiblingElement(); } } // Move on to the next manufacturer. manufacturerElement = manufacturerElement->NextSiblingElement(); } delete pDoc; return true; } //----------------------------------------------------------------------------- // // Free the XML that maps manufacturer and product IDs //----------------------------------------------------------------------------- void ManufacturerSpecific::UnloadProductXML ( ) { if (s_bXmlLoaded) { map::iterator pit = s_productMap.begin(); while( !s_productMap.empty() ) { delete pit->second; s_productMap.erase( pit ); pit = s_productMap.begin(); } map::iterator mit = s_manufacturerMap.begin(); while( !s_manufacturerMap.empty() ) { s_manufacturerMap.erase( mit ); mit = s_manufacturerMap.begin(); } s_bXmlLoaded = false; } } //----------------------------------------------------------------------------- // // Try to find and load an XML file describing the device's config params //----------------------------------------------------------------------------- bool ManufacturerSpecific::LoadConfigXML ( Node* _node, string const& _configXML ) { string configPath; Options::Get()->GetOptionAsString( "ConfigPath", &configPath ); string filename = configPath + _configXML; TiXmlDocument* doc = new TiXmlDocument(); Log::Write( LogLevel_Info, _node->GetNodeId(), " Opening config param file %s", filename.c_str() ); if( !doc->LoadFile( filename.c_str(), TIXML_ENCODING_UTF8 ) ) { delete doc; Log::Write( LogLevel_Info, _node->GetNodeId(), "Unable to find or load Config Param file %s", filename.c_str() ); return false; } Node::QueryStage qs = _node->GetCurrentQueryStage(); if( qs == Node::QueryStage_ManufacturerSpecific1 ) { _node->ReadDeviceProtocolXML( doc->RootElement() ); } else { if( !_node->m_manufacturerSpecificClassReceived ) { _node->ReadDeviceProtocolXML( doc->RootElement() ); } _node->ReadCommandClassesXML( doc->RootElement() ); } delete doc; return true; } //----------------------------------------------------------------------------- // // Reload previously discovered device configuration. //----------------------------------------------------------------------------- void ManufacturerSpecific::ReLoadConfigXML ( ) { if( Node* node = GetNodeUnsafe() ) { if (!s_bXmlLoaded) LoadProductXML(); uint16 manufacturerId = (uint16)strtol( node->GetManufacturerId().c_str(), NULL, 16 ); uint16 productType = (uint16)strtol( node->GetProductType().c_str(), NULL, 16 ); uint16 productId = (uint16)strtol( node->GetProductId().c_str(), NULL, 16 ); map::iterator mit = s_manufacturerMap.find( manufacturerId ); if( mit != s_manufacturerMap.end() ) { map::iterator pit = s_productMap.find( Product::GetKey( manufacturerId, productType, productId ) ); if( pit != s_productMap.end() ) { string configPath = pit->second->GetConfigPath(); if( configPath.size() > 0 ) { LoadConfigXML( node, configPath ); } } } } }