Reputation: 597
Unfortunately I'm having to interface with some firmware that is seemingly in a constant state of flux (the USB communications protocol keeps changing, the register/address map keeps changing, the various state machines keep changing, etc.).
I've managed to implement a reasonable interface, so actually communicating with the firmware isn't too difficult.
My main problem is representing the differing state machines, of which there are about a dozen. Before they changed I just had an enumeration, such as:
public enum Component1StateType
{
RESET = 0,
INITIALISING = 1,
READY = 2,
WAITING = 3
}
which I then used freely in the main code to modify the software's behaviour depending on the system state.
Now I have the problem whereby the newer firmware's Component1StateType
has changed to something like this:
public enum Component1StateType
{
RESET = 0,
INITIALISING = 1,
INITIALISED = 2,
READY = 3,
ERROR = 4,
STANDBY = 5,
WAITING = 6
}
which will then break all the previous state-handling code in the main program, which must of course support all the different versions of firmware.
I'm struggling to come up with a good way of representing these differing state machines in a way that means the main code won't be littered with things like:
if (stateMachineVersion == 1)
{
//code branch for version 1
}
else if(stateMachineVersion == 2)
{
//code branch for version 2
}
...
...
Any ideas on how best to deal with these ever-changing states?
Upvotes: 2
Views: 1128
Reputation: 21548
I have some real world experience of this as I maintain a service which has to handle thousands of concurrent connections from a variety of telematics tracking units ("modems"), some of which are variations on a theme from the same manufacturer and some of which have wildly differering protocols. When I first met this code most of the work was done in a single class which was, if I recall correctly, around 10,000 lines of code! I refactored it into an OOP model some years ago and it has been much, much easier to work with ever since.
I have an abstract base class for a Modem and derived classes of varying levels for each modem type. Also in play are several interfaces which represent various behaviours. I would suggest you refactor into something like the following:
internal abstract class Device // Generic name here as I don't know what kind of device you are talking to {
// Common code here
// abstract and/or virtual members here for the different behaviours in the various firmware versions
}
internal sealed class DeviceFirmwareA : Device
{
// Private, firmware-specific enumerations here
// Overrides here
}
internal sealed class DeviceFirmwareB : Device
{
// Private, firmware-specific enumerations here
// Overrides here
}
Of course you will also need:
I'm sorry I'm vague on the additional steps as I'd need to see the existing code in question but if you go down this route I'm sure you can work it out!
Upvotes: 1
Reputation:
Hmm, my proposal: Add missing states after stock value of the old version:
public enum Component1StateType
{
RESET = 0,
INITIALISING = 1,
READY = 2,
WAITING = 4,
INITIALISED = 5,
// etc
}
It won't broke old version.
Next, prepare attribute, that could be used in such way:
public enum Component1StateType
{
RESET = 0,
INITIALISING = 1,
[StateMapping(MachineStateVersion = 2, Value = 3)]
READY = 2,
[StateMapping(MachineStateVersion = 2, Value = 4)]
WAITING = 3,
[StateMapping(MachineStateVersion = 2, Value = 2)]
INITIALISED = 5,
// etc
}
The attribute could be used multiple times for various range of machnie state version. Then prepare extension method:
public static bool IsInState(this Enum enum, int machineStateVersion, Component1StateType value)
{
// fetch proper mapped value via reflection from attribute basing on machineState
}
Usage:
Component1StateType currentState = // val from some;
if (currentState.IsInState(CURRENT_MACHINE_STATE_VERSION,
Component1StateType.INITIALISING))
{
//
}
Pros
Cons
Upvotes: 0
Reputation: 726479
Make an interface for your state machine, and expose an enum
at the top level without providing any numeric values to the program outside the state machine class:
interface HardwareConnector {
Component1StateType CurrentState {get;}
... // Other properties and methods
}
public enum Component1StateType {
Reset
, Initializing
, Initialized
, Ready
, Error
, Standby
, Waiting
}
Make different implementations of the HardwareConnector
interface depending on the version of hardware that they need to support. Your code should be programming to the HardwareConnector
interface. The only place where the code should "know" about class-per-hardware-version implementations is initialization, where you detect the hardware on the other end.
Inside each implementation class you can have a private enum
disconnected from the Component1StateType
enumeration visible at the interface level.
internal enum Component1StateTypeV1 {
RESET = 0,
INITIALISING = 1,
READY = 2,
WAITING = 3
}
internal enum Component1StateTypeV2 {
RESET = 0,
INITIALISING = 1,
INITIALISED = 2,
READY = 3,
ERROR = 4,
STANDBY = 5,
WAITING = 6
}
Internals of the class use private enum
values for doing their work, and then translate its private value to the public value in the implementation of the State
getter:
public Component1StateTypeV {
get {
switch (internalState) {
case Component1StateTypeV1.RESET : return Component1StateTypeV.Reset;
case Component1StateTypeV1.INITIALISING : return Component1StateTypeV.Initializing;
case Component1StateTypeV1.READY : return Component1StateTypeV.Ready;
case Component1StateTypeV1.WAITING : return Component1StateTypeV.Waiting;
}
throw new InvalidOperationException();
}
}
Upvotes: 2