//----------------------------------------------------------------------------- // // SwitchMultilevel.cpp // // Implementation of the Z-Wave COMMAND_CLASS_SWITCH_MULTILEVEL // // 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 "command_classes/CommandClasses.h" #include "command_classes/SwitchMultilevel.h" #include "command_classes/WakeUp.h" #include "Defs.h" #include "Msg.h" #include "Driver.h" #include "Node.h" #include "platform/Log.h" #include "value_classes/ValueBool.h" #include "value_classes/ValueButton.h" #include "value_classes/ValueByte.h" using namespace OpenZWave; enum SwitchMultilevelCmd { SwitchMultilevelCmd_Set = 0x01, SwitchMultilevelCmd_Get = 0x02, SwitchMultilevelCmd_Report = 0x03, SwitchMultilevelCmd_StartLevelChange = 0x04, SwitchMultilevelCmd_StopLevelChange = 0x05, SwitchMultilevelCmd_SupportedGet = 0x06, SwitchMultilevelCmd_SupportedReport = 0x07 }; enum { SwitchMultilevelIndex_Level = 0, SwitchMultilevelIndex_Bright, SwitchMultilevelIndex_Dim, SwitchMultilevelIndex_IgnoreStartLevel, SwitchMultilevelIndex_StartLevel, SwitchMultilevelIndex_Duration, SwitchMultilevelIndex_Step, SwitchMultilevelIndex_Inc, SwitchMultilevelIndex_Dec }; static uint8 c_directionParams[] = { 0x00, 0x40, 0x00, 0x40 }; static char const* c_directionDebugLabels[] = { "Up", "Down", "Inc", "Dec" }; static char const* c_switchLabelsPos[] = { "Undefined", "On", "Up", "Open", "Clockwise", "Right", "Forward", "Push" }; static char const* c_switchLabelsNeg[] = { "Undefined", "Off", "Down", "Close", "Counter-Clockwise", "Left", "Reverse", "Pull" }; //----------------------------------------------------------------------------- // // Request current state from the device //----------------------------------------------------------------------------- bool SwitchMultilevel::RequestState ( uint32 const _requestFlags, uint8 const _instance, Driver::MsgQueue const _queue ) { if( _requestFlags & RequestFlag_Dynamic ) { return RequestValue( _requestFlags, 0, _instance, _queue ); } return false; } //----------------------------------------------------------------------------- // // Request current value from the device //----------------------------------------------------------------------------- bool SwitchMultilevel::RequestValue ( uint32 const _requestFlags, uint8 const _index, uint8 const _instance, Driver::MsgQueue const _queue ) { if( _index == SwitchMultilevelIndex_Level ) { if ( IsGetSupported() ) { Msg* msg = new Msg( "SwitchMultilevelCmd_Get", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() ); msg->SetInstance( this, _instance ); msg->Append( GetNodeId() ); msg->Append( 2 ); msg->Append( GetCommandClassId() ); msg->Append( SwitchMultilevelCmd_Get ); msg->Append( GetDriver()->GetTransmitOptions() ); GetDriver()->SendMsg( msg, _queue ); return true; } else { Log::Write( LogLevel_Info, GetNodeId(), "SwitchMultilevelCmd_Get Not Supported on this node"); } } return false; } //----------------------------------------------------------------------------- // // Handle a message from the Z-Wave network //----------------------------------------------------------------------------- bool SwitchMultilevel::HandleMsg ( uint8 const* _data, uint32 const _length, uint32 const _instance // = 1 ) { if( SwitchMultilevelCmd_Report == (SwitchMultilevelCmd)_data[0] ) { Log::Write( LogLevel_Info, GetNodeId(), "Received SwitchMultiLevel report: level=%d", _data[1] ); if( ValueByte* value = static_cast( GetValue( _instance, SwitchMultilevelIndex_Level ) ) ) { value->OnValueRefreshed( _data[1] ); value->Release(); } return true; } if( SwitchMultilevelCmd_SupportedReport == (SwitchMultilevelCmd)_data[0] ) { uint8 switchType1 = _data[1] & 0x1f; uint8 switchType2 = _data[2] & 0x1f; uint8 switchtype1label = switchType1; uint8 switchtype2label = switchType2; if (switchtype1label > 7) /* size of c_switchLabelsPos, c_switchLabelsNeg */ { Log::Write (LogLevel_Warning, GetNodeId(), "switchtype1label Value was greater than range. Setting to Invalid"); switchtype1label = 0; } if (switchtype2label > 7) /* sizeof c_switchLabelsPos, c_switchLabelsNeg */ { Log::Write (LogLevel_Warning, GetNodeId(), "switchtype2label Value was greater than range. Setting to Invalid"); switchtype2label = 0; } Log::Write( LogLevel_Info, GetNodeId(), "Received SwitchMultiLevel supported report: Switch1=%s/%s, Switch2=%s/%s", c_switchLabelsPos[switchtype1label], c_switchLabelsNeg[switchtype1label], c_switchLabelsPos[switchtype2label], c_switchLabelsNeg[switchtype2label] ); ClearStaticRequest( StaticRequest_Version ); // Set the labels on the values ValueButton* button; if( switchType1 ) { if( NULL != ( button = static_cast( GetValue( _instance, SwitchMultilevelIndex_Bright ) ) ) ) { button->SetLabel( c_switchLabelsPos[switchtype1label] ); button->Release(); } if( NULL != ( button = static_cast( GetValue( _instance, SwitchMultilevelIndex_Dim ) ) ) ) { button->SetLabel( c_switchLabelsNeg[switchtype1label] ); button->Release(); } } if( switchType2 ) { if( NULL != ( button = static_cast( GetValue( _instance, SwitchMultilevelIndex_Inc ) ) ) ) { button->SetLabel( c_switchLabelsPos[switchtype2label] ); button->Release(); } if( NULL != ( button = static_cast( GetValue( _instance, SwitchMultilevelIndex_Dec ) ) ) ) { button->SetLabel( c_switchLabelsNeg[switchtype2label] ); button->Release(); } } return true; } return false; } //----------------------------------------------------------------------------- // // Set the command class version //----------------------------------------------------------------------------- void SwitchMultilevel::SetVersion ( uint8 const _version ) { CommandClass::SetVersion( _version ); if( _version == 3 ) { // Request the supported switch types Msg* msg = new Msg( "SwitchMultilevelCmd_SupportedGet", 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( SwitchMultilevelCmd_SupportedGet ); msg->Append( GetDriver()->GetTransmitOptions() ); GetDriver()->SendMsg( msg, Driver::MsgQueue_Send ); // Set the request flag again - it will be cleared when we get a // response to the SwitchMultilevelCmd_SupportedGet message. SetStaticRequest( StaticRequest_Version ); } } //----------------------------------------------------------------------------- // // Set the level on a device //----------------------------------------------------------------------------- bool SwitchMultilevel::SetValue ( Value const& _value ) { bool res = false; uint8 instance = _value.GetID().GetInstance(); switch( _value.GetID().GetIndex() ) { case SwitchMultilevelIndex_Level: { // Level if( ValueByte* value = static_cast( GetValue( instance, SwitchMultilevelIndex_Level ) ) ) { res = SetLevel( instance, (static_cast(&_value))->GetValue() ); value->Release(); } break; } case SwitchMultilevelIndex_Bright: { // Bright if( ValueButton* button = static_cast( GetValue( instance, SwitchMultilevelIndex_Bright ) ) ) { if( button->IsPressed() ) { res = StartLevelChange( instance, SwitchMultilevelDirection_Up ); } else { res = StopLevelChange( instance ); } button->Release(); } break; } case SwitchMultilevelIndex_Dim: { // Dim if( ValueButton* button = static_cast( GetValue( instance, SwitchMultilevelIndex_Dim ) ) ) { if( button->IsPressed() ) { res = StartLevelChange( instance, SwitchMultilevelDirection_Down ); } else { res = StopLevelChange( instance ); } button->Release(); } break; } case SwitchMultilevelIndex_IgnoreStartLevel: { if( ValueBool* value = static_cast( GetValue( instance, SwitchMultilevelIndex_IgnoreStartLevel ) ) ) { value->OnValueRefreshed( (static_cast( &_value))->GetValue() ); value->Release(); } res = true; break; } case SwitchMultilevelIndex_StartLevel: { if( ValueByte* value = static_cast( GetValue( instance, SwitchMultilevelIndex_StartLevel ) ) ) { value->OnValueRefreshed( (static_cast( &_value))->GetValue() ); value->Release(); } res = true; break; } case SwitchMultilevelIndex_Duration: { if( ValueByte* value = static_cast( GetValue( instance, SwitchMultilevelIndex_Duration ) ) ) { value->OnValueRefreshed( (static_cast( &_value))->GetValue() ); value->Release(); } res = true; break; } case SwitchMultilevelIndex_Step: { if( ValueByte* value = static_cast( GetValue( instance, SwitchMultilevelIndex_Step ) ) ) { value->OnValueRefreshed( (static_cast( &_value))->GetValue() ); value->Release(); } res = true; break; } case SwitchMultilevelIndex_Inc: { // Inc if( ValueButton* button = static_cast( GetValue( instance, SwitchMultilevelIndex_Inc ) ) ) { if( button->IsPressed() ) { res = StartLevelChange( instance, SwitchMultilevelDirection_Inc ); } else { res = StopLevelChange( instance ); } button->Release(); } break; } case SwitchMultilevelIndex_Dec: { // Dec if( ValueButton* button = static_cast( GetValue( instance, SwitchMultilevelIndex_Dec ) ) ) { if( button->IsPressed() ) { res = StartLevelChange( instance, SwitchMultilevelDirection_Dec ); } else { res = StopLevelChange( instance ); } button->Release(); } break; } } return res; } //----------------------------------------------------------------------------- // // Update class values based in BASIC mapping //----------------------------------------------------------------------------- void SwitchMultilevel::SetValueBasic ( uint8 const _instance, uint8 const _value ) { // Send a request for new value to synchronize it with the BASIC set/report. // In case the device is sleeping, we set the value anyway so the BASIC set/report // stays in sync with it. We must be careful mapping the uint8 BASIC value // into a class specific value. // When the device wakes up, the real requested value will be retrieved. RequestValue( 0, 0, _instance, Driver::MsgQueue_Send ); if( Node* node = GetNodeUnsafe() ) { if( WakeUp* wakeUp = static_cast( node->GetCommandClass( WakeUp::StaticGetCommandClassId() ) ) ) { if( !wakeUp->IsAwake() ) { if( ValueByte* value = static_cast( GetValue( _instance, SwitchMultilevelIndex_Level ) ) ) { value->OnValueRefreshed( _value != 0 ); value->Release(); } } } } } //----------------------------------------------------------------------------- // // Set a new level for the switch //----------------------------------------------------------------------------- bool SwitchMultilevel::SetLevel ( uint8 const _instance, uint8 const _level ) { Log::Write( LogLevel_Info, GetNodeId(), "SwitchMultilevel::Set - Setting to level %d", _level ); Msg* msg = new Msg( "SwitchMultilevelCmd_Set", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true ); msg->SetInstance( this, _instance ); msg->Append( GetNodeId() ); if( ValueByte* durationValue = static_cast( GetValue( _instance, SwitchMultilevelIndex_Duration ) ) ) { uint8 duration = durationValue->GetValue(); durationValue->Release(); if( duration == 0xff ) { Log::Write( LogLevel_Info, GetNodeId(), " Duration: Default" ); } else if( duration >= 0x80 ) { Log::Write( LogLevel_Info, GetNodeId(), " Duration: %d minutes", duration - 0x7f ); } else { Log::Write( LogLevel_Info, GetNodeId(), " Duration: %d seconds", duration ); } msg->Append( 4 ); msg->Append( GetCommandClassId() ); msg->Append( SwitchMultilevelCmd_Set ); msg->Append( _level ); msg->Append( duration ); } else { msg->Append( 3 ); msg->Append( GetCommandClassId() ); msg->Append( SwitchMultilevelCmd_Set ); msg->Append( _level ); } msg->Append( GetDriver()->GetTransmitOptions() ); GetDriver()->SendMsg( msg, Driver::MsgQueue_Send ); return true; } //----------------------------------------------------------------------------- // // Start the level changing //----------------------------------------------------------------------------- bool SwitchMultilevel::StartLevelChange ( uint8 const _instance, SwitchMultilevelDirection const _direction ) { Log::Write( LogLevel_Info, GetNodeId(), "SwitchMultilevel::StartLevelChange - Starting a level change" ); uint8 length = 4; if (_direction > 3) /* size of c_directionParams, c_directionDebugLabels */ { Log::Write (LogLevel_Warning, GetNodeId(), "_direction Value was greater than range. Dropping"); return false; } uint8 direction = c_directionParams[_direction]; Log::Write( LogLevel_Info, GetNodeId(), " Direction: %s", c_directionDebugLabels[_direction] ); if( ValueBool* ignoreStartLevel = static_cast( GetValue( _instance, SwitchMultilevelIndex_IgnoreStartLevel ) ) ) { if( ignoreStartLevel->GetValue() ) { // Set the ignore start level flag direction |= 0x20; } ignoreStartLevel->Release(); } Log::Write( LogLevel_Info, GetNodeId(), " Ignore Start Level: %s", (direction & 0x20) ? "True" : "False" ); uint8 startLevel = 0; if( ValueByte* startLevelValue = static_cast( GetValue( _instance, SwitchMultilevelIndex_StartLevel ) ) ) { startLevel = startLevelValue->GetValue(); startLevelValue->Release(); } Log::Write( LogLevel_Info, GetNodeId(), " Start Level: %d", startLevel ); uint8 duration = 0; if( ValueByte* durationValue = static_cast( GetValue( _instance, SwitchMultilevelIndex_Duration ) ) ) { length = 5; duration = durationValue->GetValue(); durationValue->Release(); Log::Write( LogLevel_Info, GetNodeId(), " Duration: %d", duration ); } uint8 step = 0; if( ( SwitchMultilevelDirection_Inc == _direction ) || ( SwitchMultilevelDirection_Dec == _direction ) ) { if( ValueByte* stepValue = static_cast( GetValue( _instance, SwitchMultilevelIndex_Step ) ) ) { length = 6; step = stepValue->GetValue(); stepValue->Release(); Log::Write( LogLevel_Info, GetNodeId(), " Step Size: %d", step ); } } Msg* msg = new Msg( "SwitchMultilevelCmd_StartLevelChange", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true ); msg->SetInstance( this, _instance ); msg->Append( GetNodeId() ); msg->Append( length ); msg->Append( GetCommandClassId() ); msg->Append( SwitchMultilevelCmd_StartLevelChange ); if (GetVersion() == 2) { direction &= 0x60; } else if (GetVersion() >= 3) { /* we don't support secondary switch, so we mask that out as well */ direction &= 0xE0; } msg->Append( direction ); msg->Append( startLevel ); if( length >= 5 ) { msg->Append( duration ); } if( length == 6 ) { msg->Append( step ); } msg->Append( GetDriver()->GetTransmitOptions() ); GetDriver()->SendMsg( msg, Driver::MsgQueue_Send ); return true; } //----------------------------------------------------------------------------- // // Stop the level changing //----------------------------------------------------------------------------- bool SwitchMultilevel::StopLevelChange ( uint8 const _instance ) { Log::Write( LogLevel_Info, GetNodeId(), "SwitchMultilevel::StopLevelChange - Stopping the level change" ); Msg* msg = new Msg( "SwitchMultilevelCmd_StopLevelChange", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true ); msg->SetInstance( this, _instance ); msg->Append( GetNodeId() ); msg->Append( 2 ); msg->Append( GetCommandClassId() ); msg->Append( SwitchMultilevelCmd_StopLevelChange ); msg->Append( GetDriver()->GetTransmitOptions() ); GetDriver()->SendMsg( msg, Driver::MsgQueue_Send ); return true; } //----------------------------------------------------------------------------- // // Create the values managed by this command class //----------------------------------------------------------------------------- void SwitchMultilevel::CreateVars ( uint8 const _instance ) { if( Node* node = GetNodeUnsafe() ) { switch( GetVersion() ) { case 3: { node->CreateValueByte( ValueID::ValueGenre_User, GetCommandClassId(), _instance, SwitchMultilevelIndex_Step, "Step Size", "", false, false, 0, 0 ); node->CreateValueButton( ValueID::ValueGenre_User, GetCommandClassId(), _instance, SwitchMultilevelIndex_Inc, "Inc", 0 ); node->CreateValueButton( ValueID::ValueGenre_User, GetCommandClassId(), _instance, SwitchMultilevelIndex_Dec, "Dec", 0 ); // Fall through to version 2 } case 2: { node->CreateValueByte( ValueID::ValueGenre_System, GetCommandClassId(), _instance, SwitchMultilevelIndex_Duration, "Dimming Duration", "", false, false, 0xff, 0 ); // Fall through to version 1 } case 1: { node->CreateValueByte( ValueID::ValueGenre_User, GetCommandClassId(), _instance, SwitchMultilevelIndex_Level, "Level", "", false, false, 0, 0 ); node->CreateValueButton( ValueID::ValueGenre_User, GetCommandClassId(), _instance, SwitchMultilevelIndex_Bright, "Bright", 0 ); node->CreateValueButton( ValueID::ValueGenre_User, GetCommandClassId(), _instance, SwitchMultilevelIndex_Dim, "Dim", 0 ); node->CreateValueBool( ValueID::ValueGenre_System, GetCommandClassId(), _instance, SwitchMultilevelIndex_IgnoreStartLevel, "Ignore Start Level", "", false, false, true, 0 ); node->CreateValueByte( ValueID::ValueGenre_System, GetCommandClassId(), _instance, SwitchMultilevelIndex_StartLevel, "Start Level", "", false, false, 0, 0 ); break; } } } }