I'm not super familiar with Zenoss but when I used to used nagios for this sort of thing we'd make the c/c++ process listen on a socket and write a custom nagios plugin which would hand over diagnostic and status information.
First step is to choose the lib you want to use to make your process listen.. Something like C++ Socket Library will do for that. Nothing complicated there.. just make the process listen.
Then you have to define the response your process will send given a particular stimulus. This really meant (at least with nagios) defining the 'service' and then sending the process the signal that corresponded to that service. The simplest thing you can do is create a 'process ping' just see if you can successfully connect to the running process. If you do than the custom nagios plugin knows at least the process is still alive.
There's much more sophisticated stuff you can do but the idea is simple enough. You can write your own little lib of process listening code encapsulated within objects and pull it into your custom c++ stuff in a standardized manner whenever you build one (or all) your executables
My understanding is Zenoss can do this too.
Probably since Zenoss is python then you'll write your custom plugin for it using something like Twisted for connecting to your listening c++ executable.
Probably the best way to implement FSMs in OOP languages is sticking to the State Design Pattern
That pattern provides a general structure to design and implement FSMs, dividing the FSM in two "sides": The context and the state, being the state a hierarchy of all the different states the context can have (a context can only have a very limited amount of states at a single time, usually only one). Then, the state transitions are modeled, usually either in the client (the class that uses the context will decide when to explicitly transition to a new state, making the context instance behave differently), or in the states (when each state is responsible of checking the transition conditions and perform the actual transition). The first approach has the advantage of a lower cohesion between states, making them reusable in implementations of other FSMs, but with a much more complex client class. The second approach has the opposite side effects.
In your case, I'd model System with the role of Context, State with the role of State, and Menu (and its submenus) as different ConcreteStates. If StepSequencer and SamplePad are to be exclusive of a single menu/submenu, they'd be attributes of that individual state. Otherwise, (i.e., shared or persistent between menus), as attributes of System. That decision will depend on your actual needs.
Now, about your design (and this is a little off-topic), StepSequencer and SamplePad don't look like actual states (neither by their uses nor by their names). Personally, I'd bet it's that way just because their interface is apparently identical to that of states (def buttonPress(self, numberButton):
). If that's the reason, and you did it that way to avoid duplicating code, don't. It's okay to have code duplicated in certain occasions (e.g., when the base interface is likely to change in different directions over time, as stated by Robert C. Martin in his book Clean Architecture). Keep this in mind and consider deriving them from a different base class other than State.
Best Answer
The simplest method is to pass a callback function to the state machine that gets called when a state changes (or other event happens).
Then when it gets called you can update the gui in that function (or forward the message to the gui thread if the state machine runs in a separate thread).
The signature would be something like
void callback(State oldstate, State newstate, void* userData)
The
userData
pointer is supplied at the same time as the function pointer and provides a context for the (static) callback function. If you want you can also use astd::function
instead and keep the function pointer+userData method as overload.