openhab/openhab — pull request #1678 by cdjackson (2014-11-16 20:44:46+00:00)
Title: Zwave initialisation refactoring

This won't be merged for a while, but I've opened this PR for discussion... This PR refactors the node stage advancer that is responsible for initialising the nodes. Previously, this was scattered throughout the command classes, making it difficult to maintain, test, and modify. This has all been brought into a single class. Other changes have been made to improve performance, such as delaying battery node initialisation until mains nodes have completed.

It's still work in progress, and there will be further changes, but review and comments appreciated.

Node initialisation is handled solely within the NodeStageAdvancer. It is not
based on time - it waits for the transactions to complete. Time cannot be
used since with larger networks, it may take a long time for the
initialisation. This is especially true if there are battery nodes since the
PING phase, used to detect if a node is active, will time-out for battery
devices. A timeout takes 5 seconds, and if there are retries active, this may
be extended to 15 seconds. For a network with 8 battery nodes, this could
mean a delay of 2 minutes!

We use the 'listening' flag to prioritise the initialisation of nodes. Rather
than kicking off all nodes at the same time and have battery nodes timing out
and delaying the initialisation of mains nodes, we try and initialise nodes
that are listening first. This is checked after the protocol information
is received, and non-listening nodes are held at a WAIT state until the
transmit queue drops below 2 frames when they are allowed to proceed to PING.

The NodeStageAdvancer registers an event listener during the initialisation
of a node. This allows it to be notified when each transaction is complete,
and we can process this accordingly. The event listener is removed when we
stop initialising to reduce processor loading.

Command classes are responsible for building lists of messages needed to
initialise themselves. The command class also needs to keep track of
responses so it knows if initialisation of this stage is complete. Other than
that, the command class does not have any input into the initialisation
phase, and the sequencing of events - this is all handled here in the node
advancer class.

For each stage, the advancer builds a list of all messages that need to be
sent to the node. Since the initialisation phase is an intense period, with a
lot of messages on the network, we try and ensure that only 1 packet is
outstanding to any node at once to avoid filling up the main transmit queue
which could impact on the performance of other nodes.

Each time we receive an ACK for a message, the node advancer gets called, and
we see if this is an ACK for a message that's part of the initialisation. If
it is, the message gets removed from the list.

Each time we receive a command message, the node advancer gets called. This
is called after the command class has been updated, so at this stage we know
if the stage can be completed.

Two checks are performed to allow a node stage to advance. Firstly, we make
sure we've sent all the messages required for this phase. Sending the
messages however doesn't guarantee that we get a response, so we then run
through the stage again to make sure that the command class really is
initialised. If the second run queues no messages, then we can reliably
assume this stage is completed. If we've missed anything, then we continue
until there are no messages to send.