1 //-----------------------------------------------------------------------------
2 //
3 //	Group.cpp
4 //
5 //	A set of associations in a Z-Wave device.
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 <cstring>
29 #include "Group.h"
30 #include "Manager.h"
31 #include "Driver.h"
32 #include "Node.h"
33 #include "Notification.h"
34 #include "Options.h"
35 #include "command_classes/Association.h"
36 #include "command_classes/AssociationCommandConfiguration.h"
37 #include "command_classes/MultiChannelAssociation.h"
38 #include "platform/Log.h"
39 
40 #include "tinyxml.h"
41 
42 using namespace OpenZWave;
43 
44 
45 //-----------------------------------------------------------------------------
46 // <Group::Group>
47 // Constructor
48 //-----------------------------------------------------------------------------
Group(uint32 const _homeId,uint8 const _nodeId,uint8 const _groupIdx,uint8 const _maxAssociations)49 Group::Group
50 (
51 	uint32 const _homeId,
52 	uint8 const _nodeId,
53 	uint8 const _groupIdx,
54 	uint8 const _maxAssociations
55 ):
56 	m_homeId( _homeId ),
57 	m_nodeId( _nodeId ),
58 	m_groupIdx( _groupIdx ),
59 	m_maxAssociations( _maxAssociations ),
60 	m_auto( false ),
61     m_multiInstance( false )
62 {
63 	char str[16];
64 	snprintf( str, sizeof(str), "Group %d", m_groupIdx );
65 	m_label = str;
66 
67 	CheckAuto();
68 }
69 
70 //-----------------------------------------------------------------------------
71 // <Group::Group>
72 // Constructor (from XML)
73 //-----------------------------------------------------------------------------
Group(uint32 const _homeId,uint8 const _nodeId,TiXmlElement const * _groupElement)74 Group::Group
75 (
76 	uint32 const _homeId,
77 	uint8 const _nodeId,
78 	TiXmlElement const* _groupElement
79 ):
80 	m_homeId( _homeId ),
81 	m_nodeId( _nodeId ),
82 	m_groupIdx( 0 ),
83 	m_maxAssociations( 0 ),
84 	m_auto( false ),
85 	m_multiInstance( false )
86 {
87 	int intVal;
88 	char const* str;
89 	vector<InstanceAssociation> pending;
90 
91 
92 
93 	if( TIXML_SUCCESS == _groupElement->QueryIntAttribute( "index", &intVal ) )
94 	{
95 		m_groupIdx = (uint8)intVal;
96 	}
97 
98 	/* call this so the config can override if necessary */
99 	CheckAuto();
100 
101 	if( TIXML_SUCCESS == _groupElement->QueryIntAttribute( "max_associations", &intVal ) )
102 	{
103 		m_maxAssociations = (uint8)intVal;
104 	}
105 
106 	str = _groupElement->Attribute( "auto" );
107 	if( str )
108 	{
109 		m_auto = !strcmp( str, "true" );
110 	}
111 
112 	str = _groupElement->Attribute( "label" );
113 	if( str )
114 	{
115 		m_label = str;
116 	}
117 
118 	str = _groupElement->Attribute( "multiInstance" );
119 	if( str )
120 	{
121 		m_multiInstance = !strcmp( str, "true" );
122 	}
123 
124 	// Read the associations for this group
125 	TiXmlElement const* associationElement = _groupElement->FirstChildElement();
126 	while( associationElement )
127 	{
128 		char const* elementName = associationElement->Value();
129 		if( elementName && !strcmp( elementName, "Node" ) )
130 		{
131 
132 			if( associationElement->QueryIntAttribute( "id", &intVal ) == TIXML_SUCCESS )
133 			{
134 				InstanceAssociation association;
135 				association.m_nodeId = (uint8)intVal;
136 				if( associationElement->QueryIntAttribute( "instance", &intVal ) == TIXML_SUCCESS )
137 					association.m_instance = (uint8)intVal;
138 				else
139 					association.m_instance = 0x00;
140 
141 				pending.push_back( association );
142 			}
143 		}
144 
145 		associationElement = associationElement->NextSiblingElement();
146 	}
147 
148 	// Group must be added before OnGroupChanged is called so UpdateNodeRoutes can find it.
149 	// Since we do not want to update return routes UpdateNodeRoutes won't find the group
150 	// so nothing will go out from here. The not sending of return routes information
151 	// only works by a side effect of not finding the group.
152 	OnGroupChanged( pending );
153 }
154 
155 //-----------------------------------------------------------------------------
156 // <Group::CheckAuto>
157 // Check if we should AutoAssociate for this group
158 //-----------------------------------------------------------------------------
CheckAuto()159 void Group::CheckAuto
160 (
161 
162 )
163 {
164 	// Auto-association by default is with group 1 or 255, with group 1 taking precedence.
165 	// Group 255 is always created first, so if this is group 1, we need to turn off the
166 	// auto flag in group 255.  All this messing about is to support the various behaviours
167 	// of certain Cooper devices.
168 	if( m_groupIdx == 255 )
169 	{
170 		m_auto = true;
171 	}
172 	else if( m_groupIdx == 1 )
173 	{
174 		m_auto = true;
175 
176 		// Clear the flag from Group 255, if it exists.
177 		if( Driver* driver = Manager::Get()->GetDriver( m_homeId ) )
178 		{
179 			if( Node* node = driver->GetNodeUnsafe( m_nodeId ) )
180 			{
181 				if( Group* group = node->GetGroup( 255 ) )
182 				{
183 					group->SetAuto( false );
184 				}
185 			}
186 		}
187 	}
188 }
189 
190 
191 
192 //-----------------------------------------------------------------------------
193 // <Group::WriteXML>
194 // Write ourselves to an XML document
195 //-----------------------------------------------------------------------------
WriteXML(TiXmlElement * _groupElement)196 void Group::WriteXML
197 (
198 	TiXmlElement* _groupElement
199 )
200 {
201 	char str[16];
202 
203 	snprintf( str, 16, "%d", m_groupIdx );
204 	_groupElement->SetAttribute( "index", str );
205 
206 	snprintf( str, 16, "%d", m_maxAssociations );
207 	_groupElement->SetAttribute( "max_associations", str );
208 
209 	_groupElement->SetAttribute( "label", m_label.c_str() );
210 	_groupElement->SetAttribute( "auto", m_auto ? "true" : "false" );
211 	if( m_multiInstance )
212 	{
213 		_groupElement->SetAttribute( "multiInstance", m_multiInstance ? "true" : "false" );
214 	}
215 
216 	for( map<InstanceAssociation,AssociationCommandVec,classcomp>::iterator it = m_associations.begin(); it != m_associations.end(); ++it )
217 	{
218 		TiXmlElement* associationElement = new TiXmlElement( "Node" );
219 
220 		snprintf( str, 16, "%d", it->first.m_nodeId );
221 		associationElement->SetAttribute( "id", str );
222 		if (it->first.m_instance != 0)
223 		{
224 			snprintf( str, 16, "%d", it->first.m_instance );
225 			associationElement->SetAttribute( "instance", str );
226 		}
227 
228 		_groupElement->LinkEndChild( associationElement );
229 	}
230 }
231 
232 //-----------------------------------------------------------------------------
233 // <Group::Contains>
234 // Whether a group contains a particular node
235 //-----------------------------------------------------------------------------
Contains(uint8 const _nodeId,uint8 const _instance)236 bool Group::Contains
237 (
238 	uint8 const _nodeId,
239 	uint8 const _instance
240 )
241 {
242 	for( map<InstanceAssociation,AssociationCommandVec,classcomp>::iterator it = m_associations.begin(); it != m_associations.end(); ++it )
243 	{
244 		if ((it->first.m_nodeId == _nodeId) && (it->first.m_instance == _instance))
245 		{
246 			return true;
247 		}
248 	}
249 	return false;
250 }
251 
252 //-----------------------------------------------------------------------------
253 // <Group::AddAssociation>
254 // Associate a node with this group
255 //-----------------------------------------------------------------------------
AddAssociation(uint8 const _nodeId,uint8 const _instance)256 void Group::AddAssociation
257 (
258 	uint8 const _nodeId,
259 	uint8 const _instance
260 )
261 {
262 	if( Driver* driver = Manager::Get()->GetDriver( m_homeId ) )
263 	{
264 		if( Node* node = driver->GetNodeUnsafe( m_nodeId ) )
265 		{
266 			MultiChannelAssociation* cc = static_cast<MultiChannelAssociation*>( node->GetCommandClass( MultiChannelAssociation::StaticGetCommandClassId() ));
267 			if( cc && IsMultiInstance() )
268 			{
269 				cc->Set( m_groupIdx, _nodeId, _instance );
270 				cc->QueryGroup( m_groupIdx, 0 );
271 			}
272 			else if( Association* cc = static_cast<Association*>( node->GetCommandClass( Association::StaticGetCommandClassId() ) ) )
273 			{
274 				cc->Set( m_groupIdx, _nodeId );
275 				cc->QueryGroup( m_groupIdx, 0 );
276 			}
277 			else
278 			{
279 				Log::Write( LogLevel_Info, m_nodeId, "No supported Association CC found" );
280 			}
281 		}
282 	}
283 }
284 
285 //-----------------------------------------------------------------------------
286 // <Group:RemoveAssociation>
287 // Remove a node from this group
288 //-----------------------------------------------------------------------------
RemoveAssociation(uint8 const _nodeId,uint8 const _instance)289 void Group::RemoveAssociation
290 (
291 	uint8 const _nodeId,
292 	uint8 const _instance
293 )
294 {
295 	if( Driver* driver = Manager::Get()->GetDriver( m_homeId ) )
296 	{
297 		if( Node* node = driver->GetNodeUnsafe( m_nodeId ) )
298 		{
299 			MultiChannelAssociation* cc = static_cast<MultiChannelAssociation*>( node->GetCommandClass( MultiChannelAssociation::StaticGetCommandClassId() ));
300 			if( cc && IsMultiInstance() )
301 			{
302 				cc->Remove( m_groupIdx, _nodeId, _instance );
303 				cc->QueryGroup( m_groupIdx, 0 );
304 			}
305 			else if( Association* cc = static_cast<Association*>( node->GetCommandClass( Association::StaticGetCommandClassId() ) ) )
306 			{
307 					cc->Remove( m_groupIdx, _nodeId );
308 					cc->QueryGroup( m_groupIdx, 0 );
309 			}
310 			else
311 			{
312 				Log::Write( LogLevel_Info, m_nodeId, "No supported Association CC found" );
313 			}
314 		}
315 	}
316 }
317 
318 //-----------------------------------------------------------------------------
319 // <Group::OnGroupChanged>
320 // Change the group contents and notify the watchers
321 //-----------------------------------------------------------------------------
OnGroupChanged(vector<uint8> const & _associations)322 void Group::OnGroupChanged
323 (
324 	vector<uint8> const& _associations
325 )
326 {
327 	vector<InstanceAssociation> instanceAssociations;
328 	uint8 i;
329 	for( i=0; i<_associations.size(); ++i )
330 	{
331 		InstanceAssociation association;
332 		association.m_nodeId	= _associations[i];
333 		association.m_instance  = 0x00;
334 		instanceAssociations.push_back( association );
335 	}
336 	OnGroupChanged(instanceAssociations);
337 	instanceAssociations.clear();
338 }
339 
340 //-----------------------------------------------------------------------------
341 // <Group::OnGroupChanged>
342 // Change the group contents and notify the watchers
343 //-----------------------------------------------------------------------------
OnGroupChanged(vector<InstanceAssociation> const & _associations)344 void Group::OnGroupChanged
345 (
346 	vector<InstanceAssociation> const& _associations
347 )
348 {
349 	bool notify = false;
350 
351 	// If the number of associations is different, we'll save
352 	// ourselves some work and clear the old set now.
353 	if( _associations.size() != m_associations.size() )
354 	{
355 		m_associations.clear();
356 		notify = true;
357 	}
358 	else
359 	{
360 		// Handle initial group creation case
361 		if ( _associations.size() == 0 && m_associations.size() == 0 )
362 		{
363 			notify = true;
364 		}
365 	}
366 
367 	// Add the new associations.
368 	uint8 oldSize = (uint8)m_associations.size();
369 
370 	uint8 i;
371 	for( i=0; i<_associations.size(); ++i )
372 	{
373 		m_associations[_associations[i]] = AssociationCommandVec();
374 	}
375 
376 	if( (!notify) && ( oldSize != m_associations.size() ) )
377 	{
378 		// The number of nodes in the original and new groups is the same, but
379 		// the number of associations has grown. There must be different nodes
380 		// in the original and new sets of nodes in the group.  The easiest way
381 		// to sort this out is to clear the associations and add the new nodes again.
382 		m_associations.clear();
383 		for( i=0; i<_associations.size(); ++i )
384 		{
385 			m_associations[_associations[i]] = AssociationCommandVec();
386 		}
387 		notify = true;
388 	}
389 
390 	if( notify )
391 	{
392 		// If the node supports COMMAND_CLASS_ASSOCIATION_COMMAND_CONFIGURATION, we need to request the command data.
393 		if( Driver* driver = Manager::Get()->GetDriver( m_homeId ) )
394 		{
395 			if( Node* node = driver->GetNodeUnsafe( m_nodeId ) )
396 			{
397 				if( AssociationCommandConfiguration* cc = static_cast<AssociationCommandConfiguration*>( node->GetCommandClass( AssociationCommandConfiguration::StaticGetCommandClassId() ) ) )
398 				{
399 					for( map<InstanceAssociation,AssociationCommandVec,classcomp>::iterator it = m_associations.begin(); it != m_associations.end(); ++it )
400 					{
401 						cc->RequestCommands( m_groupIdx, it->first.m_nodeId );
402 					}
403 				}
404 			}
405 		}
406 
407 		// Send notification that the group contents have changed
408 		Notification* notification = new Notification( Notification::Type_Group );
409 		notification->SetHomeAndNodeIds( m_homeId, m_nodeId );
410 		notification->SetGroupIdx( m_groupIdx );
411 		Manager::Get()->GetDriver( m_homeId )->QueueNotification( notification );
412 		// Update routes on remote node if necessary
413 		bool update = false;
414 		Options::Get()->GetOptionAsBool( "PerformReturnRoutes", &update );
415 		if( update )
416 		{
417 			Driver *drv = Manager::Get()->GetDriver( m_homeId );
418 			if (drv)
419 				drv->UpdateNodeRoutes( m_nodeId );
420 		}
421 	}
422 }
423 
424 //-----------------------------------------------------------------------------
425 // <Group::GetAssociations>
426 // Get a list of associations for this group
427 //-----------------------------------------------------------------------------
GetAssociations(uint8 ** o_associations)428 uint32 Group::GetAssociations
429 (
430 	uint8** o_associations
431 )
432 {
433 	size_t numNodes = m_associations.size();
434 	if( !numNodes )
435 	{
436 		*o_associations = NULL;
437 		return 0;
438 	}
439 
440 	uint8* associations = new uint8[numNodes]; // room for all associations, we only need room for the associations without instance
441 	uint32 i = 0;
442 	for( map<InstanceAssociation,AssociationCommandVec,classcomp>::iterator it = m_associations.begin(); it != m_associations.end(); ++it )
443 	{
444 		if ( it->first.m_instance == 0x00)
445 		{
446 			associations[i++] = it->first.m_nodeId;
447 		}
448 	}
449 
450 	*o_associations = associations;
451 	return (uint32) i;
452 }
453 
454 //-----------------------------------------------------------------------------
455 // <Group::GetAssociations>
456 // Get a list of associations for this group
457 //-----------------------------------------------------------------------------
GetAssociations(InstanceAssociation ** o_associations)458 uint32 Group::GetAssociations
459 (
460 	InstanceAssociation** o_associations
461 )
462 {
463 	size_t numNodes = m_associations.size();
464 	if( !numNodes )
465 	{
466 		*o_associations = NULL;
467 		return 0;
468 	}
469 
470 	InstanceAssociation* associations = new InstanceAssociation[numNodes];
471 	uint32 i = 0;
472 	for( map<InstanceAssociation,AssociationCommandVec,classcomp>::iterator it = m_associations.begin(); it != m_associations.end(); ++it )
473 	{
474 		associations[i++] = it->first;
475 	}
476 
477 	*o_associations = associations;
478 	return (uint32) numNodes;
479 }
480 
481 //-----------------------------------------------------------------------------
482 // Command methods (COMMAND_CLASS_ASSOCIATION_COMMAND_CONFIGURATION)
483 //-----------------------------------------------------------------------------
484 
485 //-----------------------------------------------------------------------------
486 // <Group::ClearCommands>
487 // Clear all the commands for the specified node
488 //-----------------------------------------------------------------------------
ClearCommands(uint8 const _nodeId,uint8 const _instance)489 bool Group::ClearCommands
490 (
491 	uint8 const _nodeId,
492 	uint8 const _instance
493 )
494 {
495 	for( map<InstanceAssociation,AssociationCommandVec,classcomp>::iterator it = m_associations.begin(); it != m_associations.end(); ++it )
496 	{
497 		if( (it->first.m_nodeId == _nodeId) && (it->first.m_instance == _instance) )
498 		{
499 			it->second.clear();
500 			return true;
501 		}
502     }
503 
504 	return false;
505 }
506 
507 //-----------------------------------------------------------------------------
508 // <Group::AddCommand>
509 // Add a command to the list for the specified node
510 //-----------------------------------------------------------------------------
AddCommand(uint8 const _nodeId,uint8 const _length,uint8 const * _data,uint8 const _instance)511 bool Group::AddCommand
512 (
513 	uint8 const _nodeId,
514 	uint8 const _length,
515 	uint8 const* _data,
516 	uint8 const _instance
517 )
518 {
519 	for( map<InstanceAssociation,AssociationCommandVec,classcomp>::iterator it = m_associations.begin(); it != m_associations.end(); ++it )
520 	{
521 		if( (it->first.m_nodeId == _nodeId) && (it->first.m_instance == _instance) )
522 		{
523 			it->second.push_back( AssociationCommand( _length, _data ) );
524 			return true;
525 		}
526 	}
527 
528 	return false;
529 }
530 
531 //-----------------------------------------------------------------------------
532 // <Group::AssociationCommand::AssociationCommand>
533 // Constructor
534 //-----------------------------------------------------------------------------
AssociationCommand(uint8 const _length,uint8 const * _data)535 Group::AssociationCommand::AssociationCommand
536 (
537 	uint8 const _length,
538 	uint8 const* _data
539 )
540 {
541 	m_data = new uint8[_length];
542 	memcpy( m_data, _data, _length );
543 }
544 
545 //-----------------------------------------------------------------------------
546 // <Group::AssociationCommand::AssociationCommand>
547 // Destructor
548 //-----------------------------------------------------------------------------
~AssociationCommand()549 Group::AssociationCommand::~AssociationCommand
550 (
551 )
552 {
553 	delete [] m_data;
554 }
555 
556 
557