Each device class must have a state machine implemented in the state_handler method. It is called by the command_handler to determine whether the requested command can be executed or not. If the state_handler returns a non-zero status then the command is not executed.
For very simple devices the state_handler has very little to do - any command can be executed at anytime. For other more complicated devices the state_handler is used to reflect the internal state of the device.
All devices must however use the state_handler to control access to the device. It should be used to reflect the availability of the device. Any device (even the simplest) should support the following two states
An example state machine for the AGPowerSupplyClass is given below, it implements the state diagram depicted in figure 5 -
/*====================================================================== Function: static long state_handler() Description: Check if the command to be executed does not violate the present state of the device. Arg(s) In: AGPowerSupply ps - device to execute command to. DevCommand cmd - command to be executed. Arg(s) Out: long *error - pointer to error code (in case of failure). =======================================================================*/ static long state_handler( ps, cmd, error) AGPowerSupply ps; DevCommand cmd; long *error; { long iret = DS_OK; long int p_state, n_state; p_state = ps->devserver.state; /* * before checking out the state machine assume that the state * doesn't change i.e. new state == old state */ n_state = p_state; switch (p_state) { case (DEVOFF) : { switch (cmd) { case (DevOn) : n_state = DEVON; break; case (DevError) : n_state = DEVFAULT; break; case (DevLocal) : n_state = DEVLOCAL; break; /* following commands are ignored in this state */ case (DevSetValue) : case (DevReadValue) : iret = DS_NOTOK; *error = DevErr_CommandIgnored; break; /* default is to allow commands */ default : break; } break; } case (DEVON) : { switch (cmd) { case (DevOff) : n_state = DEVOFF; break; case (DevError) : n_state = DEVFAULT; break; case (DevLocal) : n_state = DEVLOCAL; break; /* following commands violate the state machine */ case (DevRemote) : case (DevReset) : iret = DS_NOTOK; (*error) = DevErr_AttemptToViolateStateMachine; break; /* default is to allow commands */ default : break; } break; } case (DEVLOCAL) : { switch (cmd) { case (DevRemote) : n_state = DEVOFF; break; /* the following commands violate the state machine */ case (DevOn) : case (DevOff) : case (DevRun) : case (DevReset) : case (DevStandby) : case (DevError) : iret = DS_NOTOK; (*error) = DevErr_AttemptToViolateStateMachine; break; /* following commands are ignored */ case (DevSetValue) : iret = DS_NOTOK; *error = DevErr_CommandIgnored; break; /* default is to allow commands */ default : break; } break; } case (DEVFAULT) : { switch (cmd) { case (DevReset) : n_state = DEVOFF; break; /* the following commands violate the state machine */ case (DevOff) : case (DevRemote) : case (DevOn) : case (DevLocal) : iret = DS_NOTOK; (*error) = DevErr_AttemptToViolateStateMachine; break; /* following commands are ignored */ case (DevSetValue) : case (DevReadValue) : iret = DS_NOTOK; *error = DevErr_CommandIgnored; break; /* default is to allow commands */ default : break; } break; } default : break; } /* * update powersupply's private variable n_state so that other methods * can use it too. */ ps->devserver.n_state = n_state; #ifdef DS_DEBUG printf("state_handler(): p_state %2d n_state %2d, iret %2d\n", p_state,n_state, iret); #endif return(iret); }
Figure 5: The State Diagram for the AGPowerSupplyClass
Sub-classes of the same super-class usually represent similar
devices and should therefore have the same or similar state machine.
A diagram (like in fig. 5) representing the state machine of
each new class
should be included as part of the standard device server documentation.