Many applications require a serial communication protocol to communicate with PLCs, RTUs, instruments, weather station, etc. The implementation is often ad-hoc and often problematic, especially in recovering from fault conditions caused by bit error rates or just tripping over a cable. It seems to me that there is a common design pattern for protocol states which can be implemented in whole or part depending on the domain. I'm hoping that a generic driver can be developed from this to provide a common driver framework.
The advantage of understanding the pattern is to acquire the know how needed even in very reliable communication paths such as over USB.
The state chart above shows the full generic implementation. This state engine provides:
- Support for control of exclusive system resources such as COM or USB ports independent of the communication channel state control. That is, the channel may be opened and closed without releasing resources.
- Error recovery.
- Transitional states for asynchronous state transitions which also allow for features such as 'back-off' time enforcement on channel shut down.
- Unambiguous states to simplify client use.
- Actions to control state available in every state for immediate execution, including the transitional states.
These are described more fully in the state descriptions below.
Stopped State
The stopped state is the initial state. In this state the protocol driver has been instantiated but has not acquired any resources. The driver should be placed in this state before disposing.
Transitions from this state:
- On call to Start() transitions immediately (synchronously) to the Started state.
- Re-entrant on a call to Reset().
- A call to any other state interface method will cause a transition to the Error state.
Transitions to this state:
- From any other state, including the Error state, by a call to Reset().
- From the Started state by a call to Stop().
Started State
On entering the started state the protocol driver acquires all required resources. Typically this is opening exclusive resources such as a COM or USB port. High performance drivers may use configuration information to preallocate memory.
Transitions from this state:
- On call to Open() transitions to the transitional Opening state. It is possible that the state may also transition to the Open or Failed state prior to Open() returning.
- On call to Stop() transitions to the Stopped state. All resources are released prior to this transition.
- A call to any other state interface method will cause a transition to the Error state.
Transitions to this state:
- From any other state, including the Error state, by a call to Reset().
- From the Stopped state by a call to Start().
- Asynchronously from the Opening state by a call to Close().
- Asynchronously from the Open state, via the Closing state, by a call to Close().
- Synchronously from Failed state by a call to Close().
Opening State
The Opening state is a transitional state in which the driver establishes a communication channel with the remote device. This is protocol dependent and may be:
- Protocol transactions to open a session
- A simple poll to establish that the link exists
The time taken will vary dependent on the application from microseconds to minutes (for dial-up PSTN links). This state is beneficial even when using fast USB devices as it provides a clear validation prior to data transactions for diagnostics and channel control.
The Opening state is used when trying to open a channel for the first time (from the Started state) and when trying to reconnect a failed channel (from the Failed state).
Transitions from this state:
- On success, transitions to the Open state.
- On failure, transitions to the Failed state.
- On call to Close() immediately transitions to the Closing state.
- A call to any other state interface method will cause a transition to the Error state.
Transitions to this state:
- From the Started state Open().
- From the Failed state on a call to Open() or by a reconnect timer time out.
Open State
Data transfers may occur only in the Open state. The driver is in the Open state when communications with the remote device has been validated.
Transitions from this state:
- On communication failure, transitions to the Failed state.
- On call to Close() immediately transitions to the Closing state.
- A call to any other state interface method will cause a transition to the Error state.
Transitions to this state:
- From the Opening state on successful communications channel validation.
Failed State
The failed state provides:
- Unambiguous indication of unexpected communications failure.
- Optional implementation of a driver that can automatically reconnect.
Transitions from this state:
- When conditions for attempting a reconnect (typically a time out) transitions to the Opening state.
- On a call to Open() immediately transitions to the Opening state.
- On a call to Close() immediately transitions to the Closing state.
- A call to any other state interface method will cause a transition to the Error state.
Transitions to this state:
- From the Open state on communication failure.
Closing StateThe closing state provides orderly communication channel disconnection. Depending on the protocol and application this may include:
- Session disconnection transaction with the remote device.
- Flush pending transactions.
- Allow timing for completion of transactions already commenced.
- Implement 'back-off' timing to enforce an idle time between sessions to ensure time-out of any incomplete transactions.
Transitions from this state:
- On completion of channel/session closure, transitions to the Started state.
- On call to Close() immediately transitions to the Closing state.
- A call to any other state interface method will cause a transition to the Error state.
Transitions to this state:
- From the Opening, Open, or Failed state on a call to Close().
- Re-entrant on calls to Close().