We are doing pretty good advances on the ICS/DCS that we are doing. The current code base is using the STL std::priority_queue as operation request queue, but we are changing the task request priority each time that the communication channel is being used by any transition that requests the Profibus channel. We cannot saturate the communication channel with requests, because it runs on a very low bandwidth connection, so each request should be correctly measured, where all operations on the machinery is being directed by states machines with dynamic priorities of execution, and the STL priority queue is doing a good job. But was a little bit complex to think in terms of source code with the model presented as state diagrams and flow control diagrams on a whiteboard, and took me about one week to finish the final skeleton of the primary source code.
Now just remains operation details, such as state machine definitions for each operation that is required for the machinery operations that system will handle. The system has two main kinds of operations, automatic operations and manual operations, where manual operations have a higher priority than automatic ones, also automatic operations can be interrupted by manual operations, so the state machine that is running an automatic operation can be interrupted by any manual operation. Each state transition is monitored checking the system state, looking if a requested a manual operation should replace the current automatic operation, or the automatic operation should be interrupted by another automatic operation with a higher priority.
The algorithm — now that is implemented and not placed as diagram on the whiteboard — looks much simpler, and the basic algorithm looks as follows.
req = dev_get_request(MAIN_CONFIG, NUM_DEVICES); if (req != NULL) { queue_task(req); } while (!channel && (req = check_queue())) { if (req->done) { break; } req->device->run(req); if (!req->channel) { continue; } sts = io_write(io_ctx); if (EVEN(sts)) { errh_Fatal("ra_fcm: io_write error, %m", sts); fcm_io_alarm(req->device, FCM_IO_WRITE); } sts = io_read(io_ctx); if (EVEN(sts)) { errh_Fatal("ra_fcm: io_read error, %m", sts); fcm_io_alarm(req->device, FCM_IO_READ); } if (req->nobreak) { channel = false; } else { channel = true; } }
The dev_get_request method checks if there is a pending request, automatic or manual, if there is a pending request, it is being queued, and immediately the process starts processing the queued items, where they can be finished in one step or not, and the device runs the state machine referenced by the request, if the state machine transition uses the I/O channel, the ProView methods io_write and io_read are called, then, if the transition requests a no-break operation to make a continuous usage of the communication channel, it keeps running the state machine. Otherwise the state machine breaks its operation and seeks another operation request in the queue. The check_queue method seeks the proper prioritized request on the queue, where its priorities are changing inside the state machine on the run method.
But the state machine runner — placed on the device as run method — as an abstraction of the managed device by the priority queue, where there are several devices to manage, schedules their tasks using a simple algorithm. The real complexity is placed on the operation of each device, and the algorithm itself is an abstraction of the generalized state machine that we are running.
if (dev_state == DEV_ST_INACTIVE) { dev_state = DEV_ST_ONLINE; } request->nobreak = false; machine->channel = false; request->channel = false; for (c = 0; c < machine->num_states; c++) { tr = &(machine->states); if (tr->curr_st != dev_state) { continue; } if (!tr->guard(tr, machine, request, this)) { break; } if (!operate(tr, machine, request)) { break; } errh_Info(st_msg_fmt, fcu_state, tr->next_st, tr->curr_st); dev_state = tr->next_st; if (tr->next_st == machine->final_state) { request->done = true; break; } if (tr->channel) { chn = true; request->nobreak = tr->nobreak; machine->channel = true; request->channel = true; break; } } return chn;
The guard checks environmental variables, and if there are good environmental conditions to operate, then it starts operating the device according to the current transition, that holds the action as enum type, with a simple switch statement to check the action that should be taken on the state transition. On a successful operation and the proper prioritization, the queue is reordered outside the state machine runner, allowing queued operations to trigger alarms if they are not finished, among other similar error checking and environment checking routines. So, the system keeps working, alerting the user through the HMI if there is any condition that never meets the required operation. ProView has several ways to alert users about the system conditions, and we are using almost everything that we can. I think that this system is very well structured and now running dynamic priorities is even more stable than using plain task requests, mainly due to environmental variables. This scheduler looks preemptive, but it is not.