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