1 //-----------------------------------------------------------------------------
2 //
3 //	Value.cpp
4 //
5 //	Base class for all OpenZWave Value Classes
6 //
7 //	Copyright (c) 2010 Mal Lansell <openzwave@lansell.org>
8 //
9 //	SOFTWARE NOTICE AND LICENSE
10 //
11 //	This file is part of OpenZWave.
12 //
13 //	OpenZWave is free software: you can redistribute it and/or modify
14 //	it under the terms of the GNU Lesser General Public License as published
15 //	by the Free Software Foundation, either version 3 of the License,
16 //	or (at your option) any later version.
17 //
18 //	OpenZWave is distributed in the hope that it will be useful,
19 //	but WITHOUT ANY WARRANTY; without even the implied warranty of
20 //	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 //	GNU Lesser General Public License for more details.
22 //
23 //	You should have received a copy of the GNU Lesser General Public License
24 //	along with OpenZWave.  If not, see <http://www.gnu.org/licenses/>.
25 //
26 //-----------------------------------------------------------------------------
27 
28 #include "tinyxml.h"
29 #include "Manager.h"
30 #include "Driver.h"
31 #include "Localization.h"
32 #include "Node.h"
33 #include "Notification.h"
34 #include "Msg.h"
35 #include "Bitfield.h"
36 #include "value_classes/Value.h"
37 #include "platform/Log.h"
38 #include "command_classes/CommandClass.h"
39 #include <ctime>
40 #include "Options.h"
41 
42 namespace OpenZWave
43 {
44 	namespace Internal
45 	{
46 		namespace VC
47 		{
48 
49 			static char const* c_genreName[] =
50 			{ "basic", "user", "config", "system", "invalid" };
51 
52 			static char const* c_typeName[] =
53 			{ "bool", "byte", "decimal", "int", "list", "schedule", "short", "string", "button", "raw", "bitset", "invalid type" };
54 
55 //-----------------------------------------------------------------------------
56 // <Value::Value>
57 // Constructor
58 //-----------------------------------------------------------------------------
Value(uint32 const _homeId,uint8 const _nodeId,ValueID::ValueGenre const _genre,uint8 const _commandClassId,uint8 const _instance,uint16 const _index,ValueID::ValueType const _type,string const & _label,string const & _units,bool const _readOnly,bool const _writeOnly,bool const _isSet,uint8 const _pollIntensity)59 			Value::Value(uint32 const _homeId, uint8 const _nodeId, ValueID::ValueGenre const _genre, uint8 const _commandClassId, uint8 const _instance, uint16 const _index, ValueID::ValueType const _type, string const& _label, string const& _units, bool const _readOnly, bool const _writeOnly, bool const _isSet, uint8 const _pollIntensity) :
60 					m_min(0), m_max(0), m_refreshTime(0), m_verifyChanges(false), m_refreshAfterSet(true), m_id(_homeId, _nodeId, _genre, _commandClassId, _instance, _index, _type), m_units(_units), m_readOnly(_readOnly), m_writeOnly(_writeOnly), m_isSet(_isSet), m_affectsLength(0), m_affects(), m_affectsAll(false), m_checkChange(false), m_pollIntensity(_pollIntensity)
61 			{
62 				SetLabel(_label);
63 			}
64 
65 //-----------------------------------------------------------------------------
66 // <Value::Value>
67 // Constructor (from XML)
68 //-----------------------------------------------------------------------------
Value()69 			Value::Value() :
70 					m_min(0), m_max(0), m_refreshTime(0), m_verifyChanges(false), m_refreshAfterSet(true), m_readOnly(false), m_writeOnly(false), m_isSet(false), m_affectsLength(0), m_affects(), m_affectsAll(false), m_checkChange(false), m_pollIntensity(0)
71 			{
72 			}
73 
74 //-----------------------------------------------------------------------------
75 // <Value::~Value>
76 // Destructor
77 //-----------------------------------------------------------------------------
~Value()78 			Value::~Value()
79 			{
80 				if (m_affectsLength > 0)
81 				{
82 					delete[] m_affects;
83 				}
84 			}
85 
86 //-----------------------------------------------------------------------------
87 // <Value::ReadXML>
88 // Apply settings from XML
89 //-----------------------------------------------------------------------------
ReadXML(uint32 const _homeId,uint8 const _nodeId,uint8 const _commandClassId,TiXmlElement const * _valueElement)90 			void Value::ReadXML(uint32 const _homeId, uint8 const _nodeId, uint8 const _commandClassId, TiXmlElement const* _valueElement)
91 			{
92 				int intVal;
93 
94 				ValueID::ValueGenre genre = Value::GetGenreEnumFromName(_valueElement->Attribute("genre"));
95 				ValueID::ValueType type = Value::GetTypeEnumFromName(_valueElement->Attribute("type"));
96 
97 				uint8 instance = 1;
98 				if (TIXML_SUCCESS == _valueElement->QueryIntAttribute("instance", &intVal))
99 				{
100 					instance = (uint8) intVal;
101 				}
102 
103 				uint16 index = 0;
104 				if (TIXML_SUCCESS == _valueElement->QueryIntAttribute("index", &intVal))
105 				{
106 					/* index is only 10 bytes in the ValueID class */
107 					index = (uint16) (intVal & 0x3FF);
108 				}
109 
110 				m_id = ValueID(_homeId, _nodeId, genre, _commandClassId, instance, index, type);
111 
112 				char const* label = _valueElement->Attribute("label");
113 				if (label)
114 				{
115 					SetLabel(label);
116 				}
117 
118 				char const* units = _valueElement->Attribute("units");
119 				if (units)
120 				{
121 					m_units = units;
122 				}
123 
124 				char const* readOnly = _valueElement->Attribute("read_only");
125 				if (readOnly)
126 				{
127 					m_readOnly = !strcmp(readOnly, "true");
128 				}
129 
130 				char const* writeOnly = _valueElement->Attribute("write_only");
131 				if (writeOnly)
132 				{
133 					m_writeOnly = !strcmp(writeOnly, "true");
134 				}
135 
136 				if (TIXML_SUCCESS == _valueElement->QueryIntAttribute("poll_intensity", &intVal))
137 				{
138 					m_pollIntensity = (uint8) intVal;
139 				}
140 
141 				char const* affects = _valueElement->Attribute("affects");
142 				if (affects)
143 				{
144 					if (m_affectsLength != 0)
145 					{
146 						delete[] m_affects;
147 					}
148 					m_affectsLength = 0;
149 					if (!strcmp(affects, "all"))
150 					{
151 						m_affectsAll = true;
152 					}
153 					else
154 					{
155 						size_t len = strlen(affects);
156 						if (len > 0)
157 						{
158 							for (size_t i = 0; i < len; i++)
159 							{
160 								if (affects[i] == ',')
161 								{
162 									m_affectsLength++;
163 								}
164 								else if (affects[i] < '0' || affects[i] > '9')
165 								{
166 									Log::Write(LogLevel_Info, "Improperly formatted affects data: \"%s\"", affects);
167 									break;
168 								}
169 							}
170 							m_affectsLength++;
171 							m_affects = new uint8[m_affectsLength];
172 							unsigned int j = 0;
173 							for (int i = 0; i < m_affectsLength; i++)
174 							{
175 								m_affects[i] = atoi(&affects[j]);
176 								while (j < len && affects[j] != ',')
177 								{
178 									j++;
179 								}
180 								j++;
181 							}
182 						}
183 					}
184 				}
185 
186 				char const* verifyChanges = _valueElement->Attribute("verify_changes");
187 				if (verifyChanges)
188 				{
189 					m_verifyChanges = !strcmp(verifyChanges, "true");
190 				}
191 
192 				if (TIXML_SUCCESS == _valueElement->QueryIntAttribute("min", &intVal))
193 				{
194 					m_min = intVal;
195 				}
196 
197 				if (TIXML_SUCCESS == _valueElement->QueryIntAttribute("max", &intVal))
198 				{
199 					m_max = intVal;
200 				}
201 
202 				TiXmlElement const* helpElement = _valueElement->FirstChildElement();
203 				while (helpElement)
204 				{
205 					char const* str = helpElement->Value();
206 					if (str && !strcmp(str, "Help"))
207 					{
208 						Localization::Get()->ReadXMLVIDHelp(m_id.GetNodeId(), _commandClassId, index, -1, helpElement);
209 					}
210 					if (str && !strcmp(str, "Label"))
211 					{
212 						Localization::Get()->ReadXMLVIDLabel(m_id.GetNodeId(), _commandClassId, index, -1, helpElement);
213 					}
214 					helpElement = helpElement->NextSiblingElement();
215 				}
216 			}
217 
218 //-----------------------------------------------------------------------------
219 // <Value::WriteXML>
220 // Write ourselves to an XML document
221 //-----------------------------------------------------------------------------
WriteXML(TiXmlElement * _valueElement)222 			void Value::WriteXML(TiXmlElement* _valueElement)
223 			{
224 				char str[16];
225 
226 				_valueElement->SetAttribute("type", GetTypeNameFromEnum(m_id.GetType()));
227 				_valueElement->SetAttribute("genre", GetGenreNameFromEnum(m_id.GetGenre()));
228 
229 				snprintf(str, sizeof(str), "%d", m_id.GetInstance());
230 				_valueElement->SetAttribute("instance", str);
231 
232 				snprintf(str, sizeof(str), "%d", (m_id.GetIndex() & 0x3FF));
233 				_valueElement->SetAttribute("index", str);
234 
235 				_valueElement->SetAttribute("label", GetLabel().c_str());
236 				_valueElement->SetAttribute("units", m_units.c_str());
237 				_valueElement->SetAttribute("read_only", m_readOnly ? "true" : "false");
238 				_valueElement->SetAttribute("write_only", m_writeOnly ? "true" : "false");
239 				_valueElement->SetAttribute("verify_changes", m_verifyChanges ? "true" : "false");
240 
241 				snprintf(str, sizeof(str), "%d", m_pollIntensity);
242 				_valueElement->SetAttribute("poll_intensity", str);
243 
244 				snprintf(str, sizeof(str), "%d", m_min);
245 				_valueElement->SetAttribute("min", str);
246 
247 				snprintf(str, sizeof(str), "%d", m_max);
248 				_valueElement->SetAttribute("max", str);
249 
250 				if (m_affectsAll)
251 				{
252 					_valueElement->SetAttribute("affects", "all");
253 				}
254 				else if (m_affectsLength > 0)
255 				{
256 					string s;
257 					for (int i = 0; i < m_affectsLength; i++)
258 					{
259 						snprintf(str, sizeof(str), "%d", m_affects[i]);
260 						s = s + str;
261 						if (i + 1 < m_affectsLength)
262 						{
263 							s = s + ",";
264 						}
265 
266 					}
267 					_valueElement->SetAttribute("affects", s.c_str());
268 				}
269 				Localization::Get()->WriteXMLVIDHelp(m_id.GetNodeId(), m_id.GetCommandClassId(), m_id.GetIndex(), -1, _valueElement);
270 			}
271 
272 //-----------------------------------------------------------------------------
273 // <Value::Set>
274 // Set a new value in the device
275 //-----------------------------------------------------------------------------
Set()276 			bool Value::Set()
277 			{
278 				// nothing to do if this is a read-only value (return false to indicate an error)
279 				if (IsReadOnly())
280 				{
281 					return false;
282 				}
283 
284 				// retrieve the driver, node and commandclass object for this value
285 				bool res = false;
286 				Node* node = NULL;
287 				if (Driver* driver = Manager::Get()->GetDriver(m_id.GetHomeId()))
288 				{
289 					node = driver->GetNodeUnsafe(m_id.GetNodeId());
290 					if (node != NULL)
291 					{
292 						if (Internal::CC::CommandClass* cc = node->GetCommandClass(m_id.GetCommandClassId()))
293 						{
294 							Log::Write(LogLevel_Info, m_id.GetNodeId(), "Value::Set - %s - %s - %d - %d - %s", cc->GetCommandClassName().c_str(), this->GetLabel().c_str(), m_id.GetIndex(), m_id.GetInstance(), this->GetAsString().c_str());
295 							// flag value as set and queue a "Set Value" message for transmission to the device
296 							res = cc->SetValue(*this);
297 
298 							if (res)
299 							{
300 								if (!IsWriteOnly())
301 								{
302 									// queue a "RequestValue" message to update the value
303 									if (m_refreshAfterSet) {
304 										cc->RequestValue( 0, m_id.GetIndex(), m_id.GetInstance(), Driver::MsgQueue_Send );
305 									}
306 								}
307 								else
308 								{
309 									// There is a "bug" here in that write only values
310 									// never send a notification about the value changing.
311 									// For sleeping devices it may not change until the
312 									// device wakes up at some point in the future.
313 									// So when is the right time to change it?
314 									if (m_affectsAll)
315 									{
316 										node->RequestAllConfigParams(0);
317 									}
318 									else if (m_affectsLength > 0)
319 									{
320 										for (int i = 0; i < m_affectsLength; i++)
321 										{
322 											node->RequestConfigParam(m_affects[i]);
323 										}
324 									}
325 								}
326 							}
327 						}
328 					}
329 				}
330 
331 				return res;
332 			}
333 
334 //-----------------------------------------------------------------------------
335 // <Value::OnValueRefreshed>
336 // A value in a device has been refreshed
337 //-----------------------------------------------------------------------------
OnValueRefreshed()338 			void Value::OnValueRefreshed()
339 			{
340 				if (IsWriteOnly())
341 				{
342 					return;
343 				}
344 
345 				if (Driver* driver = Manager::Get()->GetDriver(m_id.GetHomeId()))
346 				{
347 					m_isSet = true;
348 
349 					bool bSuppress;
350 					Options::Get()->GetOptionAsBool("SuppressValueRefresh", &bSuppress);
351 					if (!bSuppress)
352 					{
353 						// Notify the watchers
354 						Notification* notification = new Notification(Notification::Type_ValueRefreshed);
355 						notification->SetValueId(m_id);
356 						driver->QueueNotification(notification);
357 					}
358 				}
359 			}
360 
361 //-----------------------------------------------------------------------------
362 // <Value::OnValueChanged>
363 // A value in a device has changed
364 //-----------------------------------------------------------------------------
OnValueChanged()365 			void Value::OnValueChanged()
366 			{
367 				if (IsWriteOnly())
368 				{
369 					return;
370 				}
371 
372 				if (Driver* driver = Manager::Get()->GetDriver(m_id.GetHomeId()))
373 				{
374 					m_isSet = true;
375 
376 					// Notify the watchers
377 					Notification* notification = new Notification(Notification::Type_ValueChanged);
378 					notification->SetValueId(m_id);
379 					driver->QueueNotification(notification);
380 				}
381 				/* Call Back to the Command Class that this Value has changed, so we can search the
382 				 * TriggerRefreshValue vector to see if we should request any other values to be
383 				 * refreshed.
384 				 */
385 				Node* node = NULL;
386 				if (Driver* driver = Manager::Get()->GetDriver(m_id.GetHomeId()))
387 				{
388 					node = driver->GetNodeUnsafe(m_id.GetNodeId());
389 					if (node != NULL)
390 					{
391 						if (Internal::CC::CommandClass* cc = node->GetCommandClass(m_id.GetCommandClassId()))
392 						{
393 							cc->CheckForRefreshValues(this);
394 						}
395 					}
396 				}
397 
398 			}
399 
400 //-----------------------------------------------------------------------------
401 // <Value::GetGenreEnumFromName>
402 // Static helper to get a genre enum from a string
403 //-----------------------------------------------------------------------------
GetGenreEnumFromName(char const * _name)404 			OpenZWave::ValueID::ValueGenre Value::GetGenreEnumFromName(char const* _name)
405 			{
406 				ValueID::ValueGenre genre = ValueID::ValueGenre_System;
407 				if (_name)
408 				{
409 					for (int i = 0; i < (int) ValueID::ValueGenre_Count; ++i)
410 					{
411 						if (!strcmp(_name, c_genreName[i]))
412 						{
413 							genre = (ValueID::ValueGenre) i;
414 							break;
415 						}
416 					}
417 				}
418 
419 				return genre;
420 			}
421 
422 //-----------------------------------------------------------------------------
423 // <Value::GetGenreNameFromEnum>
424 // Static helper to get a genre enum as a string
425 //-----------------------------------------------------------------------------
GetGenreNameFromEnum(ValueID::ValueGenre _genre)426 			char const* Value::GetGenreNameFromEnum(ValueID::ValueGenre _genre)
427 			{
428 				return c_genreName[_genre];
429 			}
430 
431 //-----------------------------------------------------------------------------
432 // <Value::GetTypeEnumFromName>
433 // Static helper to get a type enum from a string
434 //-----------------------------------------------------------------------------
GetTypeEnumFromName(char const * _name)435 			OpenZWave::ValueID::ValueType Value::GetTypeEnumFromName(char const* _name)
436 			{
437 				ValueID::ValueType type = ValueID::ValueType_Bool;
438 				if (_name)
439 				{
440 					for (int i = 0; i <= (int) ValueID::ValueType_Max; ++i)
441 					{
442 						if (!strcmp(_name, c_typeName[i]))
443 						{
444 							type = (ValueID::ValueType) i;
445 							break;
446 						}
447 					}
448 				}
449 
450 				return type;
451 			}
452 
453 //-----------------------------------------------------------------------------
454 // <Value::GetTypeNameFromEnum>
455 // Static helper to get a type enum as a string
456 //-----------------------------------------------------------------------------
GetTypeNameFromEnum(ValueID::ValueType _type)457 			char const* Value::GetTypeNameFromEnum(ValueID::ValueType _type)
458 			{
459 				if (_type > (int) ValueID::ValueType_Max)
460 				{
461 					Log::Write(LogLevel_Warning, "Value::GetTypeNameFromEnum is out of range: %d", (int) _type);
462 					return c_typeName[ValueID::ValueType_Max + 1];
463 				}
464 
465 				return c_typeName[_type];
466 			}
467 
468 //-----------------------------------------------------------------------------
469 // <Value::VerifyRefreshedValue>
470 // Check a refreshed value
471 //-----------------------------------------------------------------------------
VerifyRefreshedValue(void * _originalValue,void * _checkValue,void * _newValue,ValueID::ValueType _type,int _originalValueLength,int _checkValueLength,int _newValueLength)472 			int Value::VerifyRefreshedValue(void* _originalValue, void* _checkValue, void* _newValue, ValueID::ValueType _type, int _originalValueLength, // = 0,
473 					int _checkValueLength, // = 0,
474 					int _newValueLength // = 0
475 					)
476 			{
477 				// TODO: this is pretty rough code, but it's reused by each value type.  It would be
478 				// better if the actions were taken (m_value = _value, etc.) in this code rather than
479 				// in the calling routine as a result of the return value.  In particular, it's messy
480 				// to be setting these values after the refesh or notification is sent.  With some
481 				// focus on the actual variable storage, we should be able to accomplish this with
482 				// memory functions.  It's really the strings that make things complicated(?).
483 				// if this is the first read of a value, assume it is valid (and notify as a change)
484 				if (!IsSet())
485 				{
486 					Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Initial read of value");
487 					Value::OnValueChanged();
488 					return 2;		// confirmed change of value
489 				}
490 				else
491 				{
492 					switch (_type)
493 					{
494 						case ValueID::ValueType_Button:			// Button is stored as a bool
495 						case ValueID::ValueType_Bool:			// bool
496 						{
497 							Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Refreshed Value: old value=%s, new value=%s, type=%s", *((bool*) _originalValue) ? "true" : "false", *((uint8*) _newValue) ? "true" : "false", GetTypeNameFromEnum(_type));
498 							break;
499 						}
500 						case ValueID::ValueType_Byte:			// byte
501 						{
502 							Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Refreshed Value: old value=%d, new value=%d, type=%s", *((uint8*) _originalValue), *((uint8*) _newValue), GetTypeNameFromEnum(_type));
503 							break;
504 						}
505 						case ValueID::ValueType_Decimal:		// decimal is stored as a string, so treat it as a string here
506 						case ValueID::ValueType_String:			// string
507 						{
508 							Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Refreshed Value: old value=%s, new value=%s, type=%s", ((string*) _originalValue)->c_str(), ((string*) _newValue)->c_str(), GetTypeNameFromEnum(_type));
509 							break;
510 						}
511 						case ValueID::ValueType_Short:			// short
512 						{
513 							Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Refreshed Value: old value=%d, new value=%d, type=%s", *((short*) _originalValue), *((short*) _newValue), GetTypeNameFromEnum(_type));
514 							break;
515 						}
516 						case ValueID::ValueType_List:			// List Type is treated as a int32
517 						case ValueID::ValueType_Int:			// int32
518 						case ValueID::ValueType_BitSet:			// BitSet
519 						{
520 							Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Refreshed Value: old value=%d, new value=%d, type=%s", *((int32*) _originalValue), *((int32*) _newValue), GetTypeNameFromEnum(_type));
521 							break;
522 						}
523 						case ValueID::ValueType_Raw:			// raw
524 						{
525 							Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Refreshed Value: old value=%x, new value=%x, type=%s", _originalValue, _newValue, GetTypeNameFromEnum(_type));
526 							break;
527 						}
528 						case ValueID::ValueType_Schedule:		// Schedule Type
529 						{
530 							Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Refreshed Value: old value=%s, new value=%s, type=%s", _originalValue, _newValue, GetTypeNameFromEnum(_type));
531 							/* we cant support verifyChanges yet... so always unset this */
532 							m_verifyChanges = false;
533 							break;
534 						}
535 					}
536 				}
537 				m_refreshTime = time( NULL);	// update value refresh time
538 
539 				// check whether changes in this value should be verified (since some devices will report values that always
540 				// change, where confirming changes is difficult or impossible)
541 				Log::Write(LogLevel_Detail, m_id.GetNodeId(), "Changes to this value are %sverified", m_verifyChanges ? "" : "not ");
542 
543 				if (!m_verifyChanges)
544 				{
545 					// since we're not checking changes in this value, notify ValueChanged (to be on the safe side)
546 					Value::OnValueChanged();
547 					return 2;				// confirmed change of value
548 				}
549 
550 				// see if the value has changed (result is used whether checking change or not)
551 				bool bOriginalEqual = false;
552 				switch (_type)
553 				{
554 					case ValueID::ValueType_Decimal:		// Decimal is stored as a string
555 					case ValueID::ValueType_String:			// string
556 						bOriginalEqual = (strcmp(((string*) _originalValue)->c_str(), ((string*) _newValue)->c_str()) == 0);
557 						break;
558 					case ValueID::ValueType_Short:			// short
559 						bOriginalEqual = (*((short*) _originalValue) == *((short*) _newValue));
560 						break;
561 					case ValueID::ValueType_List:			// List Type is treated as a int32
562 					case ValueID::ValueType_Int:			// int
563 						bOriginalEqual = (*((int32*) _originalValue) == *((int32*) _newValue));
564 						break;
565 					case ValueID::ValueType_Byte:			// uint8
566 						bOriginalEqual = (*((uint8*) _originalValue) == *((uint8*) _newValue));
567 						break;
568 					case ValueID::ValueType_Button:			// Button is stored as a bool
569 					case ValueID::ValueType_Bool:			// bool
570 						bOriginalEqual = (*((bool*) _originalValue) == *((bool*) _newValue));
571 						break;
572 					case ValueID::ValueType_Raw:			// raw
573 						bOriginalEqual = (_originalValueLength == _newValueLength);	// first check length of arrays
574 						if (bOriginalEqual)
575 							bOriginalEqual = (memcmp(_originalValue, _newValue, _newValueLength) == 0);	// if length is the same, then check content
576 						break;
577 					case ValueID::ValueType_Schedule:		// Schedule
578 						/* Should not get here */
579 						break;
580 					case ValueID::ValueType_BitSet:			// BitSet
581 						bOriginalEqual = (((Bitfield *) _originalValue)->GetValue() == ((Bitfield *) _newValue)->GetValue());
582 						break;
583 				}
584 
585 				// if this is the first refresh of the value, test to see if the value has changed
586 				if (!IsCheckingChange())
587 				{
588 					if (bOriginalEqual)
589 					{
590 						// values are the same, so signal a refresh and return
591 						Value::OnValueRefreshed();
592 						return 0;			// value hasn't changed
593 					}
594 
595 					// values are different, so flag this as a verification refresh and queue it
596 					Log::Write(LogLevel_Info, m_id.GetNodeId(), "Changed value (possible)--rechecking");
597 					SetCheckingChange(true);
598 					Manager::Get()->RefreshValue(GetID());
599 					return 1;				// value has changed (to be confirmed)
600 				}
601 				else		// IsCheckingChange is true if this is the second read of a potentially changed value
602 				{
603 					// if the second read is the same as the first read, the value really changed
604 					bool bCheckEqual = false;
605 					switch (_type)
606 					{
607 						case ValueID::ValueType_Decimal:		// Decimal is stored as a string
608 						case ValueID::ValueType_String:			// string
609 							bCheckEqual = (strcmp(((string*) _checkValue)->c_str(), ((string*) _newValue)->c_str()) == 0);
610 							break;
611 						case ValueID::ValueType_Short:			// short
612 							bCheckEqual = (*((short*) _checkValue) == *((short*) _newValue));
613 							break;
614 						case ValueID::ValueType_List:			// List Type is treated as a int32
615 						case ValueID::ValueType_Int:			// int32
616 							bCheckEqual = (*((int32*) _checkValue) == *((int32*) _newValue));
617 							break;
618 						case ValueID::ValueType_Byte:			// uint8
619 							bCheckEqual = (*((uint8*) _checkValue) == *((uint8*) _newValue));
620 							break;
621 						case ValueID::ValueType_Button:			// Button is stored as a bool
622 						case ValueID::ValueType_Bool:			// bool
623 							bCheckEqual = (*((bool*) _checkValue) == *((bool*) _newValue));
624 							break;
625 						case ValueID::ValueType_Raw:
626 							bCheckEqual = (_checkValueLength == _newValueLength);	// first check length of arrays
627 							if (bCheckEqual)
628 								bCheckEqual = (memcmp(_checkValue, _newValue, _newValueLength) == 0);	// if length is the same, then check content
629 							break;
630 						case ValueID::ValueType_Schedule:
631 							/* Should not get here */
632 							break;
633 						case ValueID::ValueType_BitSet:			// BitSet
634 							bCheckEqual = (((Bitfield *) _checkValue)->GetValue() == ((Bitfield *) _newValue)->GetValue());
635 							;
636 							break;
637 					}
638 					if (bCheckEqual)
639 					{
640 						Log::Write(LogLevel_Info, m_id.GetNodeId(), "Changed value--confirmed");
641 						SetCheckingChange(false);
642 
643 						// update the saved value and send notification
644 						Value::OnValueChanged();
645 						return 2;
646 					}
647 
648 					// if the second read is the same as the original value, the first read is assumed to have been in error
649 					// log this situation, but don't change the value or send a ValueChanged Notification
650 					if (bOriginalEqual)
651 					{
652 						Log::Write(LogLevel_Info, m_id.GetNodeId(), "Spurious value change was noted.");
653 						SetCheckingChange(false);
654 						Value::OnValueRefreshed();
655 						return 0;
656 					}
657 
658 					// the second read is different than both the original value and the checked value...retry
659 					// keep trying until we get the same value twice
660 					Log::Write(LogLevel_Info, m_id.GetNodeId(), "Changed value (changed again)--rechecking");
661 					SetCheckingChange(true);
662 
663 					// save a temporary copy of value and re-read value from device
664 					Manager::Get()->RefreshValue(GetID());
665 					return 1;
666 				}
667 			}
668 
GetHelp() const669 			std::string const Value::GetHelp() const
670 			{
671 				return Localization::Get()->GetValueHelp(m_id.GetNodeId(), m_id.GetCommandClassId(), m_id.GetIndex(), -1);
672 			}
SetHelp(string const & _help,string const lang)673 			void Value::SetHelp(string const& _help, string const lang)
674 			{
675 				Localization::Get()->SetValueHelp(m_id.GetNodeId(), m_id.GetCommandClassId(), m_id.GetIndex(), -1, _help, lang);
676 			}
677 
GetLabel() const678 			std::string const Value::GetLabel() const
679 			{
680 				return Localization::Get()->GetValueLabel(m_id.GetNodeId(), m_id.GetCommandClassId(), m_id.GetIndex(), -1);
681 			}
SetLabel(string const & _label,string const lang)682 			void Value::SetLabel(string const& _label, string const lang)
683 			{
684 				Localization::Get()->SetValueLabel(m_id.GetNodeId(), m_id.GetCommandClassId(), m_id.GetIndex(), -1, _label, lang);
685 			}
686 		} // namespace VC
687 	} // namespace Internal
688 } // namespace OpenZWave
689 
690