Notifications
Clear all

Making a Baby Gate Alarm

18 Posts
3 Users
1 Likes
1,724 Views
robotBuilder
(@robotbuilder)
Member
Joined: 5 years ago
Posts: 2043
 

@jker

I'm quoting from the other thread... because I'm pretty sure it was supposed to be posted here...

Oops πŸ™‚

There is a pretty loud buzzer attached. And the time from initially opening the gate to the first audio alert is 5 seconds. I have found that if you make a buzzer go off immediately, part of the mind "normalizes" the sound, and the children are more likely to completely ignore it.

Ok seems you have it all covered although I personally would have an auto close and lock after someone used the door if only for possible electronic failure. Also you clearly are an advanced programmer so I am struggling to make sense of the terminology and actual code implementation design. I think it is a stand alone project for the more advanced programmer so well done.

I could imagine an even fancier gate would open automatically to anyone taller than the crawling child so it didn't have to be left open while someone was carrying several jugs of milk downstairs.

Β 

Β 


   
ReplyQuote
frogandtoad
(@frogandtoad)
Member
Joined: 5 years ago
Posts: 1458
 

@jker

Posted by: @frogandtoad
Posted by: @frogandtoad

Incorporating sub-states where it makes sense to do so, is indeed a valid part of good state machine design. And in this instance, I don’t see why "paused" should be it’s own top level state?Β  Since you're pausing the alarm, doesn't it make more sense to be a substate within the alarm state itself?

I think we have a misunderstanding of the user interface. Pause as a substate to Alarm (what is implied in this "Alarm" state?) would indicate that you had to be in the alarm state to pause the system, which is completely contrary to the UI design. The user should be able to walk up to the door, push the button, open the door and leave it open while carrying several jugs of milk downstairs at 10:30pm while the children are (supposedly) asleep. At the same time, there is a 5min timeout so that the gate is not left open accidentally.

I am implying that the system should already be in a known (powered on) state to begin with, before pressing any buttons to generate an event (this is mostly recognised as a system being in an idle state).Β  It implies that when the user walks up to the door and presses the button, an event is generated, and passed to the alarm system, asking it to pause for a preconfigured period of time.

Posted by: @jker

In addition, if the user forgets to push the button, and the alarm does sound, the user should be able to push the pause button to turn it off. This is two separate transitions, and leads directly to its own state.

Certainly, the user should be able to do that.

Posted by: @jker

This is not, of course, the only possible state machine for this user interface. The "UNKNOWN" state in particular is unnecessary if you're willing to do a bit more work in setup and force us into either the open or closed state for a single loop iteration when unpausing the system.

I can accept an IDLE state, but I don't think there should ever be an UNKNOWN state in this situation.

Posted by: @frogandtoad
Posted by: @frogandtoad

I know that, and that's why I said it needs to be handled, i.e:- typically with a guard condition.

Guard conditions are an augmentation of formal state machines to allow branching logic in the state transition logic. They are never strictly necessary... a formal state machine can incorporate everything they do by means of an expanded state set.

The whole idea of guard conditions (and good design), are so that you don't transition into other states erroneously... which is what makes state machines so powerful to prove that ones system will functions as expected per the design - I don't see leaving them out as being a good idea, nor supporting state machine principles.

Posted by: @jker

Cue long aside about FSM theory...

[snip]

Posted by: @frogandtoad
Posted by: @frogandtoad

Can you please explain how can a stuck state can occur?, if the gate is open, or even closed for that matter?
Can you also explain, how can there be an unknown state at all?

"Stuck" or "Wedged"... (I apparently used different terms in the code and in the diagram) is the state that results when the system times out after trying to notify the humans in every way it knows how. The Alexa app is configured to alert our phones after a few notifications... and this state is more or less designed to handle the case where someone leaves the gate open as we leave the house to prevent spamming the phones. An argument can be made that it should continue to beep the buzzer on a timer.Β  One of the nice things about this design is that it took me more time to write this paragraph than to make that change.

If you're leaving the home (presumably the baby is with you and not at home alone), then just bail out the alarm system after set iterations, and put the system into an END state... what's your alternative?

Posted by: @frogandtoad
Posted by: @frogandtoad

Unless your writing template specializations and special functors, lambada's are just an unnecessary obfuscation imo, especially when there are no comments.

Lambdas are simple syntactic sugar over struct-functors. Unlike C++ functions, all the defaults for lambdasΒ  (mutability and scope, mainly) are correct. In modern programming, lambdas are recommended unless you have a specific reason not to use them. In the embedded world, that usually means specific memory placement such as with interrupt handlers. In this code, the other place I did not use them is for the state-transition actions, due to the type-nature of lambdas and the overhead of std::function wrappers. This can be worked around, but there are enough advanced features in this code without type-eliding lambdas.

I've seen many sites refer to lambdas as such ("syntactic sugar"), where they show you expressions to make their point look good, but in reality, they hide simpler constructs to do the same thing.

Even the creator of C++ (Bjarne Stroustrup) himself acknowledges that lambdas can make you write absolute crap code, as well as good code, so please don't think that by incorporating the latest you beaut language features in your code somehow makes your code any better or elegant otherwise... readability over complexity wins hands down any day of the week in all programming circles, unless you have a specific reason to do otherwise.

Posted by: @jker

If I were to slightly simplify the loop code at the cost of some performance,Β  I end up with the following:

You mention performance and elegance, yet you have at least five lambdas in your code, and you use them in at least 4 searches, whereas that could have been handled with a single associative container class like; for example, a std::map<enum, FuncPtr> myMachine;, where you don't have to search at all... it's just there already when you reference the key, as simple as the following:

myMachine[state or event]();
Posted by: @jker

The neat thing about this code is that with approximately 4 lines of logic, the entire state machine is implemented, handling every event processed by the system.

Which one is neater?

Posted by: @frogandtoad
Posted by: @frogandtoad

Is this your real code or something else?

This comment is uncalled for. Check out the "Help" link at http://coliru.stacked-crooked.com and see what coliru is, consider why you might get a preprocessor inclusion failure when including an arduino header on a general invocation of g++. I was just using the site as a pastebin.

Uh, how so?

The code at the link you posted clearly indicated a failure to compile your posted code:

g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp:10:10: fatal error: SinricPro.h: No such file or directory
10 | #include "SinricPro.h"
| ^~~~~~~~~~~~~
compilation terminated.

Given the message shown, how was it uncalled for to ask if that was the real (final version) of your code?


   
ReplyQuote
jker
 jker
(@jker)
Member
Joined: 3 years ago
Posts: 82
Topic starter  
Posted by: @frogandtoad

I am implying that the system should already be in a known (powered on) state to begin with, before pressing any buttons to generate an event (this is mostly recognised as a system being in an idle state).Β  It implies that when the user walks up to the door and presses the button, an event is generated, and passed to the alarm system, asking it to pause for a preconfigured period of time.

This is a flowchart or process model of a system, not a state machine. There is nothing wrong with this approach, but it is not the approach taken here.

Β 

Posted by: @frogandtoad

I am implying that the system should already be in a known (powered on) state to begin with, before pressing any buttons to generate an event (this is mostly recognised as a system being in an idle state)

....

Posted by: @frogandtoad

I can accept an IDLE state, but I don't think there should ever be an UNKNOWN state in this situation.

....

Posted by: @frogandtoad

If you're leaving the home (presumably the baby is with you and not at home alone), then just bail out the alarm system after set iterations, and put the system into an END state... what's your alternative?

Frankly, this seems like quibbling about state names: idle vs unknown, END vs wedged/stuck. I'm certainly not going to waste much time defending mine, considering that I couldn't stay consistent between a chart and the code. πŸ™‚

Β 

Posted by: @frogandtoad

The whole idea of guard conditions (and good design), are so that you don't transition into other states erroneously... which is what makes state machines so powerful to prove that ones system will functions as expected per the design - I don't see leaving them out as being a good idea, nor supporting state machine principles.

I think we're probably coming from different backgrounds on what a state machine is. My introduction to what I call a "finite state machine" is from mathematics and comp sci theory. (ie, the (Ξ£, S, S0, F, Ξ΄) quintuple) In particular the state transition function (Ξ΄) is a simply "state + input -> output state" function. These types of "pure" finite state machines may be useful for monitoring the state of a system, but we in programming often want to make them a bit more directly useful. For this reason, we adjust the state transition function (ie, state table) with a "side effect" or action that will be executed on state transition. A mathematician might regard this as a minor perversion of the whole idea.

But to make state machines tractable for practical systems, we also want to reduce the state space. This is where ideas like guards come in. Guards are, if not an invention of UML, at least were popularized by UML as a way to help simplify the description of some systems. ( https://en.wikipedia.org/wiki/UML_state_machine#Guard_conditions) What is fundamentally happening is that we are reducing our number of states by splitting our state information between our "current state" and "other information", wherever that is. This necessitates the use of guards, because our state machine now needs an additional "input".

But in same way that push down automata can be proven equivalent to turing machines, guards can be proven to add no more functionality to a state machine... only convenience. The same concept is handled by the theoretical/mathematical state machine by just using extra states and transitions. A guard is a notational shortcut, not a fundamental change to the concept. I have, personally, not found them to be very useful, as they add an additional level of state complexity. The exceptions have all been in cases where I need to depend on an external API that does not generate reasonable events, so I have to query it during transitions in guards.

Posted by: @frogandtoad

I've seen many sites refer to lambdas as such ("syntactic sugar"), where they show you expressions to make their point look good, but in reality, they hide simpler constructs to do the same thing.

This is simply false. The "simpler constructs" generate more code, less clearly, and almost universally with worse optimizations. The "worst case" for lambdas is to operate the same as a free function, a situation you rarely see outside of scenarios where lambdas are shared between translation units.

The easiest example is non-trivial applications of find via the find_if standard function. Lambda syntax isn't even that difficult.

[<capture list>] (<parameter list>) -> <optional return type> { code to execute }

The only slightly odd thing about it is the concept of a "capture list". This is useful because lambdas replace the C++98 functor syntax which accomplished capture like this:

    
struct MyFunctor {
int checkVariable;
bool operator()(OtherClass const &target) { return target.variable == checkVariable; }
};
bigArray::const_iterator it = std::find_if(bigArray.begin(), bigArray.end(), MyFunctor{target});

The entire point of a functor is that you have some controlled state that is being accessed inside of your "function object". They also happen to be very convenient syntax for non-capturing small functions, but that's not the motivation for the system.

Posted by: @frogandtoad

You mention performance and elegance, yet you have at least five lambdas in your code, and you use them in at least 4 searches, whereas that could have been handled with a single associative container class like; for example, a std::map<enum, FuncPtr> myMachine;, where you don't have to search at all... it's just there already when you reference the key, as simple as the following:

There are a few things wrong here.Β  For one, there is nothing "inelegant" about using language features a decade after they have become standard. This is particularly true when you are using them in just about the most iconic way possible (functors for std:: algorithms).

In addition, a std::map is just hiding the calls to std::lower_bound from you, it's just a balanced heap-allocated binary-ish tree. The idea that you avoid "searching" by using an associative container is an illusion.Β  If you are in a situation where the search overhead is worth thinking about, you get the same or slightly better results by using std::lower_bound and sorting your table at compile time due to some rather unfortunate design/specification decisions in std::map. (std::unordered_map is slightly better, but still not great)

A minor nit is also that to be useful, your associative container needs to handle state/event lookup, so it would look more like std::map<std::pair<State, Event>, std::pair<State, FunctionPtr>>, which is basically results in the same chart I wrote as a constexpr'd std::map, with two more sets of {}s per line.

There is a serious improvement that could be made on this state machine. There are several implementations of C++ state machines that operate entirely through a series of clever constexpr constructions such that the entire thing converts directly to code. In this way you avoid all lookup overhead. This seemed unnecessary for this case, but would be an improvement.

"A resistor makes a lightbulb and a capacitor makes an explosion when connected wrong"
"There are two types of electrical engineers, those intentionally making antennas and those accidentally doing so."


   
ReplyQuote
Page 2 / 2