Automaton - Reactive State Machine Framework for Arduino
Read the Wiki for more information.
Reactive State Machine Framework for Arduino
Home Page: https://github.com/tinkerspy/Automaton/wiki
License: MIT License
Automaton - Reactive State Machine Framework for Arduino
Read the Wiki for more information.
Hi,
I have a use case where I have a timer (millis in this case), and during runtime (while within a state and not going to another state) I'd like to reset this timer such that it won't return true for expired() until the amount of time configured in the timer has passed since the time of the reset I want to do. Basically saying for example "please don't expire until after another ten seconds".
It seems that the timer uses state_millis in the Machine class, so I guess the closest I can get is to do timer.set(myTimeout + millis() - state_millis)
which stores a new timeout value that works for the purpose when expired()
evaluates it, but is there a cleaner way already present? If not, perhaps we should introduce a reset() on the timer, that does basically the previous?
On a related note I realize that since everything uses millis() which will overflow after somewhere around 48-50 days, a timer value cannot be larger than that and still "work" correctly.
I am using the D13 pin on the 328p to drive a FET that drives a speaker. This is shared with the onboard LED and has been working fine. As soon as I include this library (without even defining or using any part of it, the D13 pin becomes useless. The onboard led barely blinks when I am toggling it to control the buzzer. I thought the pin was dead but if I just comment out the Automation.h file in the main INO and recompile the pin starts working. Is some portion of the code initializing the D13 pin as an input instead of output or enabling SPI or a shared function that would conflict even when I have not defined or called any portion of the library?
I've been using the Automaton library on an Attiny85 since before the "Big Changes" commit that removed the tiny factory and tiny machines. I recently updated the Automaton library to 121b4df (currently the latest commit) and the one sketch I have for an Attiny85 started failing with the following error.
.pioenvs/trinket5/Automaton/Atm_servo.hpp:4:19: fatal error: Servo.h: No such file or directory
#include <Servo.h>
I'm not using anything Servo related in my sketch. This is the extent to which I use the Automaton library.
Appliance app;
Atm_button lampOnBtn;
Atm_button lampDemoBtn;
Atm_led mp3PlayBtn;
Atm_led mp3NextBtn;
Atm_led mp3PrevBtn;
After trying a number of previous commits, I found that 0171a98 was the last one where it compiled without issue. As soon as I try to use 0ed4594 or beyond, compilation starts failing with the following error.
.pioenvs/trinket5/Servo/avr/Servo.cpp: In function 'void __vector_3()':
.pioenvs/trinket5/Servo/avr/Servo.cpp:82:44: error: cannot convert 'volatile uint8_t* {aka volatile unsigned char*}' to 'volatile uint16_t* {aka volatile unsigned int*}' for argument '2' to 'void handle_interrupts(timer16_Sequence_t, volatile uint16_t*, volatile uint16_t*)'
handle_interrupts(_timer1, &TCNT1, &OCR1A);
^
.pioenvs/trinket5/Servo/avr/Servo.cpp: In function 'void initISR(timer16_Sequence_t)':
.pioenvs/trinket5/Servo/avr/Servo.cpp:129:5: error: 'TCCR1B' was not declared in this scope
TCCR1B = _BV(CS11); // set prescaler of 8
^
.pioenvs/trinket5/Servo/avr/Servo.cpp:136:5: error: 'TIFR1' was not declared in this scope
TIFR1 |= _BV(OCF1A); // clear any pending interrupts;
^
.pioenvs/trinket5/Servo/avr/Servo.cpp:137:5: error: 'TIMSK1' was not declared in this scope
TIMSK1 |= _BV(OCIE1A) ; // enable the output compare interrupt
^
icons: *** [.pioenvs/trinket5/Servo/avr/Servo.o] Error 1
I can certainly use 0171a98 for this specific sketch, but is there anything else I can do on my end? Or is there something Automaton can do?
Tinkerspy,
IMHO, I am getting very close to getting my very important project done using the Automaton making some "not as critical" feature compromises along the way. Getting the dynamic memory above 90% is now hampering my ability to use the Automaton's nifty trace on my specific wasco custom machine. I need to shut off Automaton's trace so I am working a bit more in the dark for the final touches. Any help or suggestions along that line for me "still learning" what is specific to Automaton?
I notice in your LCD project you have an Automaton version of the Adafruit LCD Shield library. Did that help save dynamic memory? My lcd library is the I2C version found at www.brainy-bits.com.
Thanks
Somewhat by my lonesome I am getting very far along on an existing mechanical switched Traffic Light scenario. Our friend TinkerSpy's custom machine starting point example is the main guiding star. By scenario, I mean something that is more for a low traffic Roundabout that hopefully will do more to accommodate runners, walkers, & bicyclists along with motorists relying on strategically placed sensors & a giant replica of the brainy-bits.com I2C LCD setup. This TrafficLight version relies more on four separate sensors (simple on off switching) than on the three standby timer EVTs. These four sensors act as LOW ACTIVE switches to allow for a more simple IMHO wiring hookup using the Atmel 328p internal pullups or the automatic internal pullups within the eight module external relay bank: https://arduino-info.wikispaces.com/ArduinoPower Specifically the project is with the UNO clone with SparkFun Red Board & Arduino 1.6.7.
Since all of you have easy access to the cpp & h files of the TrafficLight example ... & since they have not been changed by me at this point ... I did not attach them. Since the "at this point in time" INO sketch is where I did for better or worse all of the modifications ... that is what I will cut & past properly formatted in this post.
The below sketch compiles & is set up with the serial debugger that will make it so I probably won't need to send a video or a 3D simulation of the External Relays' "Green, Yellow, Red" leds going on & off.
The main difficulty that I am having is utilizing the IDX or perhaps I should be somehow establishing STATE better in the cpp & h files. I am falling short when it comes to the "if" statement seeing when the state of GREEN, YELLOW or RED is current. That is where the trigger for the 2 countdowns & the simple message for the eventual lcd is needed to have those displays done at the proper time. Here is a snippet of the serial monitor display showing only IDX == 0 consequence of the if statement arrangement. I did make an honest effort to use INDEX like showing in the ATML code & STATE trying to glean from others' code on this specific github.
swt1NumTest 50
0 Switch TRAFFICLIGHT@2F3 from *NONE* to GREEN on *NONE* (1 cycles in 0 ms)
Time left: 02:29
Time left: 02:28
Time left: 02:27
Time left: 02:26
Time left: 02:25
Time left: 02:24
Time left: 02:23
Time left: 02:22
Time left: 02:21
10038 Switch TRAFFICLIGHT@2F3 from GREEN to YELLOW on EVT_YELLOW (38059 cycles in 10008 ms)
Time left: 02:20
Time left: 02:19
Time left: 02:18
12495 Switch TRAFFICLIGHT@2F3 from YELLOW to RED on EVT_RED (9102 cycles in 2428 ms)
Here is the complete INO sketch before adding any LCD Library or declaring which possibly would be better off in the cpp & h files???? Hopefully cut & paste ... if helpful ... into your own ino paired with TinkerSpy's existing trafficlight cpp & h.
#include <Automaton.h>
#include "Atm_trafficlight.h"
/* Trafficlight State Machine including 2 TimeCountdowns and 1 Message Arduino sketch
A review of some Automaton State Machine Framework principles from a slightly different angle */
Atm_trafficlight trafficlight;
Atm_digital input1;
Atm_digital input2;
Atm_digital input3;
Atm_timer countdown;
int swt1 = 8; // assigning pin ... maybe better done in h & cpp file???
int swt1State = digitalRead(swt1); // progressing to make swt1 in a usable way.
int TCalc = 200; //For 1st adjustment to time within the pertinent repeat method.
int TCalc2 = 100; //For 2nd adjustment to time within the pertinent repeat method.
int swt1Num = 0; //Zero initialization based on pertinent sensor switch.
int idx; // The states are actually changing based on relay leds & debugger but idx stays on ZERO
void displayPlus ( int idx) {
if (swt1State == LOW) {
swt1Num = 50;
}
if (idx == 0)
{ countdown.begin( 1000 ) // Each step takes 1 second
.repeat( TCalc - swt1Num ) // Set to chosen seconds sensor swt possible subtract.
.onTimer( [] ( int idx, int v, int up ) {
char buffer[50];
sprintf( buffer, "Time left: %02d:%02d", v / 60, v % 60 );
Serial.println( buffer );
})
.start();
}
if (swt1State == LOW) {
swt1Num = 50;
}
if (idx == 1)
{ countdown.begin( 1000 ) // Each step takes 1 second
.repeat( TCalc2 - swt1Num ) // Set to chosen seconds
.onTimer( [] ( int idx, int v, int up ) {
char buffer[50];
sprintf( buffer, "Time left: %02d:%02d", v / 60, v % 60 );
Serial.println( buffer );
})
.start();
}
if (idx == 2 )
Serial.println( "NoCountdown Just message" );
}
void setup() {
pinMode (swt1, INPUT_PULLUP);
Serial.begin( 9600 );
trafficlight.trace( Serial );
trafficlight.begin( 4, 5, 6 ) // Pins 4, 5 & 6
.automatic( -1, -1, -1 ); // Green 5s, yellow 2s, red 5s via -1s Only sensors are used in this rural ROUNDABOUT intersection.
input1.begin (10, 500, true, true)
.onChange(true, trafficlight, trafficlight.EVT_GREEN); //GREEN trafficlight turned on by intelligent sensor(1)that acts as a switch
input2.begin (11, 500, true, true)
.onChange(true, trafficlight, trafficlight.EVT_YELLOW); //YELLOW trafficlight turned on by intelligent sensor(2) that acts as a switch
input3.begin (12, 500, true, true)
.onChange(true, trafficlight, trafficlight.EVT_RED);//RED trafficlight turned on by intelligently placed sensor(3) that acts as a switch
displayPlus (idx);
Serial.print ("swt1NumTest ");
Serial.println ( swt1Num) ; // for some reason only logs 0 if placed at start of setup function
}
void loop() {
automaton.run();
}
I can't see any invocation of the sleep_mode()
or any other function. Are you sure, that putting Arduino to sleep is supported by this library? Or should I do it manually? If so, what is the purpose of the Machine::sleep
member variable?
Hi,
I'm currently evaluating your framework to try it out on a Cortex M0 and an ESP32, both of which have INPUT_PULLDOWN capabilities (which is more suitable to my project).
I'll quickly hack it locally as my time is limited, but maybe you would consider extending Atm_button to provide an extra parameter for the PINMODE, for a minor update?
Best
Hi tinkerspy,
Really like what you have done with this library, very cool !!
I am working on a home automation / smarthome project, and I would like to create all the nodes as Automaton factories. Figure this is a pretty common use case, other people can benefit from your feedback here.
One thing that will be consistent for all nodes is sending and receiving messages over a 2.4GHz wireless network. (RF24 + RF24Network )
Been playing around with a generic RF24Network machine which can be run within a factory and communicate and be triggered by other machines. This is what I have so far
It seems to be working(states changing, events/actions triggering) but not convinced the state table is right.
Will likely have more questions about this and related topics.
Thanks for any help!
I'm pondering what a good balance is between using separate states for small/simple tasks a machine performs during runtime vs just putting those tasks in the ON_LOOP action of an existing state, and would like to hear your opinions.
Assume you have an info Display that shows what status a Fan is in. The Fan can be Off, Low and High, and these are the three texts that the Display show depending on the status of the Fan.
The Display has at least two states, which are OFF and ON. In the ON state it should continuously display the appropriate text reflecting the Fan status. In the OFF state it doesn't do anything.
Now, the question is which of the following you do in the Display machine, to manage the updating of the status. For the sake of discussion, this is with the background that the Fan signals the Display when its status change, so the Display will determine whether or not to update its text by looking at whether it has a FAN_CHANGED message.
To me it's a matter of what constitutes a state and how pragmatic one should be. The additional state approach may be more correct and easy to follow, but at the same time updating the text on the display is such a basic part of being a display that is turned on, that it might not warrant a separate state and it's instead better to be pragmatic and just put the update work in the loop code of the ON state and not complicate things further.
What do you guys think?
I am using a hardware debouncing circuit like this using a Hex Schmitt-Trigger Inverter:
As a result, the buttons are effectively inversed with the "Automaton button machine."
What would be the best way to account for the inversion caused by the SN74HC14 in Automaton code so that button presses and releases can be recognized correctly?
Is there some way to just flip what defines a button as "Pressed"
https://github.com/tinkerspy/Automaton/blob/master/src/Atm_player.hpp#L5
Should be _VARIANT_ARDUINO_DUE_X_
Please merge #46 too
Hi Tinkerspy,
Hopefully straightforward for an expert ... 4 questions:
When external contacts on SPDT relay make or break with the Arduino Ground (com) are read with your Automaton Process ... I am seeing that your Digital Machine (if one can get by with it) saves memory over your Button Machine.
I have a total of eight separate digital machines that are basically just reading a ground signal or loss of ground signal with some control of duration of the time the make or break change is on. To prevent electrical noise problems I am using input pullup mode on all of those pins used with your Digital Machine. Almost 100% using just ground signals.
Can you explain if changing some of your "true" to "false" can ever be helpful or do anything at all in the way the digital machine behaves.
So far I am setting pinModes & declaring them as integers etc of all eight of those above pins used with my own Automaton Machine in the ino file & the cpp & hpp files of the newly created custom machine. Could it be that a person can be overly cautious so all three files would have those 8 pinMode settings & int declarations. Could that cause more use of very scarce SRAM memory?
It seems like I might be just using the Custom Machine only for receiving push changes from your Digital Machine & with the Acceptor Machine's serial output & serial buffer held to control the moving to specific states for specific ENT outputs. Which then runs relays in a timely way & change them back & so the relays shut off by the specific action(s) usually on the specific state's EXT action. Does that mean that there is no need for a "push connector" on my Custom Machine since it seem like it really does not push anything to other machines but just receives "pushes" from the other mentioned machines (mainly your digital machines)? Maybe I am wasting a bit of SRAM by adding a push connector in my Custom Machine in your machine template creator or does it need to be there to receive pushes also???
Do I only need to declare the output pins in cpp & hpp as long as the changes in digitalWrite is only occurring within the table's ENTs & EXTs actions? Would they be declared as PRIVATE along with the event timers being declared PRIVATE?
Thanks again.
Since these things are so widely used, I thought somebody must have already written some code using them with automaton. I haven't found anything yet. Can you point me to any example using either the adafruit neopixel library or FastLED?
I am seeing some odd behavior with messages to Atm_led not being processed when they should. The following test sketch at the bottom shows the behaviour. It is a simple timer with a 5 second tick at which time it sends a message to a LED to either turn it off or on, alternately. I receive the following output:
Starting...
0 Switching LED1 from state NONE to IDLE on trigger NONE (1 cycles in 0 ms)
Sending MSG_OFF to led
Sending MSG_ON to led
10002 Switching LED1 from state IDLE to ON on trigger EVT_ON (48243 cycles in 10000 ms)
10006 Switching LED1 from state ON to IDLE on trigger EVT_OFF (1 cycles in 0 ms)
Sending MSG_OFF to led
Sending MSG_ON to led
20002 Switching LED1 from state IDLE to ON on trigger EVT_ON (48243 cycles in 9990 ms)
20006 Switching LED1 from state ON to IDLE on trigger EVT_OFF (1 cycles in 1 ms)
which shows the off message being effectively queued at 5 seconds and then processed right after the ON message at 10 seconds.
Also, there is something odd with the code in Machine::cycle()
for ( i = ATM_ON_EXIT + 1; i < state_width; i++ ) {
if ( ( read_state( state_table + ( current * state_width ) + i ) != -1 ) && ( i == state_width - 1 || event( i - ATM_ON_EXIT - 1 ) ) ) {
action( read_state( state_table + ( current * state_width ) + ATM_ON_EXIT ) );
state( read_state( state_table + ( current * state_width ) + i ) );
trigger = i - ATM_ON_EXIT - 1;
return *this;
}
}
where event() is being passed a state instead of an event.
#include <Automaton.h>
#include <Atm_led.h>
#include <Atm_timer.h>
#define UPDATE_TIMER 1
#define TOGGLE_TIMER 2
#define MYLED 13
bool allowUpdate = true;
Atm_led led;
Atm_timer toggleTimer;
Factory factory;
void setup(){
Serial.begin( 115000 );
Serial.println( "Starting..." );
led.begin( MYLED ).state( led.IDLE );
led.label( "LED1" ).onSwitch( sw, "IDLE\0ON\0START\0BLINK_OFF", "EVT_ON_TIMER\0EVT_OFF_TIMER\0EVT_COUNTER\0EVT_ON\0EVT_OFF\0EVT_BLINK\0ELSE" );
factory.add( led );
toggleTimer.begin().id( TOGGLE_TIMER ).interval( 5000 ).repeat( ATM_TIMER_OFF ).onTimer( toggleUpdate );
factory.add( toggleTimer );
}
void loop(){
factory.cycle();
}
void toggleUpdate( int id ){
if ( allowUpdate==true ){
Serial.println( "Sending MSG_OFF to led" );
led.msgWrite( led.MSG_OFF );
} else {
Serial.println( "Sending MSG_ON to led" );
led.msgWrite( led.MSG_ON );
}
allowUpdate = !allowUpdate;
}
void sw( const char label[], const char current[], const char next[], const char trigger[], uint32_t runtime, uint32_t cycles ) {
Serial.print( millis() );
Serial.print( " Switching " );
Serial.print( label );
Serial.print( " from state " );
Serial.print( current );
Serial.print( " to " );
Serial.print( next );
Serial.print( " on trigger " );
Serial.print( trigger );
Serial.print( " (" );
Serial.print( cycles );
Serial.print( " cycles in " );
Serial.print( runtime );
Serial.println( " ms)" );
}
Just checked the code and I think atm_digital & atm_button should use interrupts instead of polling the pin with digitalread() on the loop. I'd do it but I'm too a noob for toying with the code.
Thanks by the library btw, is great
In manual:
Alternatively pass an idx parameter to the callback to reuse a single callback for multiple Atm_button
void btn_change( int press, int idx ) { if ( press ) { if ( idx == 1 ) led1.toggle( led1.IDLE, led1.START ); if ( idx == 2 ) led2.toggle( led2.IDLE, led2.START ); } } void setup() { btn.begin( 11 ).onPress( btn_change, 1 ); btn.begin( 12 ).onPress( btn_change, 2 ); }
Hi,
Is there a way to define, in the machine itself, which state it should go to from *NONE*
(i.e. the first state it should enter when it hasn't been in a state before)?
I find myself wanting to go to another state than the first one in the state table, in a few machines I have. But some of them are just instantiated and started, they're never affected/triggered/changed by another party but instead just sit there doing their work by themselves.
It would be nice if there's a way to define this in the machine class itself instead of setting it in a call to state() or similar (which I suppose one could do after the .begin() in the main code).
Basically the simple sketch like this
Atm_led led1;
void setup() {
led1.begin(2);
}
void loop() {
delay(1000);
}
results in Leonardo hanging indefinitely. Worked fine in Due.
I found out commenting out this line makes it fall through: https://github.com/tinkerspy/Automaton/blob/master/src/Atm_led.cpp#L32
Is there any dark magic there? I cannot even find a state()
function for Led.
Hi TinkerSpy or other farther along Automaton person,
Can you share an Automaton specific guideline or suggestion when it comes to generating an integer variable that can be used in a Global way. The integer would need to be based on whether an Arduino pin is HIGH or LOW.
For example: If I declare int test = 2; in the sketch above the void setup () ..... I have no problem putting in (acceptor.state - test) for the repeat method of a timer countdown that is within the acceptor callback. It will then see the repeat number as the result of the acceptor.state minus the whole number. both compiling & functioning. The problem is that I need the test variable to be = 0 when a specific pin changes to LOW. The challenge is to make it so that change can be seen ... ideally within anywhere of the void setup () including within custom callback code such as within the custom Acceptor State Machine callback code that you helped me with.
I honestly did try to see examples of this specific use of an int variable & so far I am not seeing this kind of availability of a needed declared integer number derived from a potentially changiing HIGH or LOW state of an Arduino Uno pin.
Could you run some tests to see if what I described above is even possible within Automaton?
Thanks
Mike
Without doing any surgery on the trafficlight .cpp & .h file I did some improvising on the sketch variations in the Automaton Documentation.
Below is INO code where my fertile imagination has gone to seeing if added Time Countdown Code can be also be run in a reactive event driven way. Note: the Serial.flush () comment. I tried to do due diligence research looking for a method or technique that I hoped would clear the buffer & found out Arduino Ide took that ability out after 1.01 version or so. The only potential workaround (with my limited experience) that I have not tried yet is a crafted "for loop" within reassigned cursor positions on the lcd that keeps repeating lcd.clear () for that buffer run's position until that previous buffer runs out.
In my defense, I am thinking that it is plausible ... based on reliable sensors there may be a need to reset a time countdown on a traffic light possibly once & even twice. What is a better fit to use than an event driven framework such as Automaton!?
#include <Automaton.h>
#include "Atm_trafficlight.h"
Atm_trafficlight trafficlight;
Atm_button button_g, button_r;
Atm_timer firstTimeDown, secondTimeDown;
int test = 40;
void setup() {
Serial.begin( 9600 );
trafficlight.trace( Serial );
trafficlight.begin( 7, 8, 9 ) // Pins 7, 8 & 9
.automatic( -1, 8000, -1 ); // when
button_g.begin( 2 )
.onPress( trafficlight, trafficlight.EVT_GREEN ); //pin7 led1 on "kit on a shield"
firstTimeDown.begin( 1000, test ) // Each step takes 1 second
.onTimer( [] ( int idx, int v, int up ) {
char buffer[50];
sprintf( buffer, "Time left: %02d:%02d", v / 60, v % 60 );
Serial.println( buffer );
})
.start();
button_r.begin( 5 )
.onPress( trafficlight, trafficlight.EVT_YELLOW ); //pin8 led2 on "kit on a shield"
secondTimeDown.begin( 1000, 30 ) // Each step takes 1 second
.onTimer( [] ( int idx, int v, int up ) {
Serial.flush (); //this does not stop the first buffer from completing which is needed
char buffer[50];
sprintf( buffer, "Time left: %02d:%02d", v / 60, v % 60 );
Serial.println( buffer );
})
.start();
}
void loop() {
automaton.run();
}
```
I'm not sure if this is an Automaton issue by itself, but I've noticed that commands that otherwise work OK when in conjunction with the Serial stream are only recognized if an extra ' ' (blank space) is added to the end of the command when attached instead to a SoftwareSerial object instance. Again, not sure whose library is at fault here but I think it should warrant some investigation on either part.
In an action one can inspect the current
and next
members to know the current and next state (the latter only applicable in ON_EXIT), but there's AFAIK no way to know what the previous state was.
Can we get a previous
member too, so we can get this information? I'd say it should be available in ON_ENTER, ON_LOOP and ATM_ON_SWITCH as well.
In a project I make use of automaton in several classes. There is another class where I just need a timer to call a function repeatedly (other said: I just need a scheduler).
There are several solutions:
I prefer the second solution, but the atm_cb_push_t
does not allow to call a member function of the class.
Possible solutions:
Create an "interface" class (pure virtual) for callbacks and store pointers to objects of this type. E.g. something like this:
class atm_callback { void callback(int idx, int v, int up) = 0; };
This class then could be inherited by classes that needs to provide a callback.
Drawback: Only on callback possible per class.
Allow use of lambda functions that captures this pointer.
I tried to use this, but the existing signature atm_cb_push_t
does not allow a lambda function that capture this. I don't have experience with lambda functions, so I don't know what needs to be changed:
timer.onTimer([this] ( int idx, int v, int up ) {this->timerexpired();}, 0);
results in a compile error:
src/Safety.cpp:19:79: error: no matching function for call to 'Atm_timer::onTimer(Safety::init()::__lambda0, int)'
However, without this
, the code compiles fine:
timer.onTimer([] ( int idx, int v, int up ) {Serial.print("Lambda!");}, 0);
Tinkerspy &/or other Automaton person, If the project coder is running LOW on UNO pins & also wanted to save by using one less wire to & from a relay switch leg ... is it ever permissible to use the Automaton Digital Machine & the Automaton Button Machine with the same pin (9) as two separate reactions to the same change. For example: Digital to change an Automaton State & Button to change an LCD message? Both would be using Pin 9's input_pullup declaration so the change in the UNO ground presence on the pin is what the pin (9) would be reacting to. Thanks. Just checking ... just in case there is potentially a conflict within Automaton itself. Don't want to oversimplify or over complicate ... no no ... not this American Citizen. Thanks
Hi,
Is it possible to call automaton.run() in a function called by timer? (with Ticker library) I would like (for example) leds running when the loop() is not called.
I tried it with a timer called every 1/1000s. My sketch works mostly well for the led (even during wifi setup) but when a button is pressed (managed by automation), it stops...
Cheers.
Hello!
Maybe nice change the varius "const 'type'.." with "const PROGMEM 'type'.." and use the "F()" statement in trace messages. This to save a fiew byte of RAM.
I know that some people would like to integrate already existing OO code with Automaton.
For that purpose it would be nice to have some lightweight interface class for events.
The following is the sample code provided in LED machine session.
led1.begin( 4 ).blink( 500, 500, 3 ).onFinish( led2, led.EVT_BLINK );
led2.begin( 4 ).blink( 50, 50, 10 );
led1.trigger( led1.EVT_BLINK );
Then I have a weird thought that what if I want to repeat the whole process (3xSlowBlink+10xFastBlink) for 10 times or forever? I tried to add the following in the void loop() but it does not work, so any solution to that? Thanks so much :-)
void loop() {
led1.cycle();
led2.cycle();
//automaton.run();}
'Atm_trafficlight' does not name a type.
I managed to build the tutorial up to the first compile, which was successful.
The steps thereafter also made sense, but I have run into a compile problem.
I suspect it relates to differences in the generated code and the tutorial as follows:
Atm_Traffic_Light Traffic_Light;
Atm_trafficlight trafficlight;
This is a bit confusing.
Could you in your follow up please expand on the difference between the upper and lower case instances, as well as publishing a complete code for the traffic light for comparison.
Very interesting tutorial, thanks.
Gerard
TinkerSpy,
I know you are busy like the rest of us & I know I should be able to come up with the specific answer on my own . I will try to be clear on a specific question about the specific Atm_acceptor machine that you graciously shared with me & others via a YouTube. https://www.youtube.com/watch?v=vKqEeH4OvqU
I really need to find a way when I am done using the acceptor machine within my Arduino sketch to have access to the specific v integer or credit integer outside of the callback. The purpose of that ability is so I can reassign a larger or lower number for the Atm_timer repeat variable (time countdown) depending on what specific credit or v was finalized from the Atm_acceptor acceptor machine. For example if during the acceptance of coins the final amount is v == 8 to 9 range, it would need to have a different predefined variable actual number for the specific repeat int arg than if it was in the v == 10 to 12 range etc.
Below are snippets of your code that I thought ??? would make it so the final v or credit integer would be available outside of the state machine but my sketch can't see it unless it stays within the confines of the below top snippet callback.
void acceptor_callback( int idx, int v, int up ) {
do something based on the v value here;
}
Atm_acceptor& Atm_acceptor::set( int v ) {
credit = v;
return *this;
int Atm_acceptor::state( void ) {
return credit;
}
Thanks in advance,
Hopefully this will be the last snag that I will have to bother you &/or others about.
mike
I have a (set of) active-low relay which I control both via a controller pin and an external switch.
That controller pin carries two tasks:
((DDRX & _BV(Y)) || !(PINX & _BV(Y)))
if (on) DDRX |= _BV(Y); else DDRX &= ~_BV(Y)
So I'd like to have let's-name-it Atm_opendrain
which is Atm_led
without PWM parts and overridden on/off
and Atm_digital/Atm_button
with custom state
reader.
How would you suggest to realise this?
TIA
I want to control the light dark period of 3 led but it seems over flow when the time is long (above 10 hours) just like the code below.
There were no errors after complied the problem but it just cannot work.
Atm_led led1, led2, led3;
Factory factory;
unsigned long light1=43200000;
unsigned long dark1=43200000;
unsigned long light2=57600000;
unsigned long dark2=28800000;
unsigned long light3=72000000;
unsigned long dark3=14400000;
void setup() {
led1.begin( 2 ).blink( light1 ).pause( dark1 ); // Setup
led2.begin( 3 ).blink( light2).pause( dark2 );
led3.begin( 4 ).blink( light3 ).pause( dark3 );
factory.add (led1 ).add( led2 ).add( led3 ); // Add to a factory // Start blinking
led1.msgWrite( led1.MSG_BLINK );
led2.msgWrite( led2.MSG_BLINK );
led3.msgWrite( led3.MSG_BLINK );
}
void loop() {
factory.cycle();
}
The command list for Atm_command is generally defined as:
const char cmdlist[] =
If I try to move it to progmem to save ram:
const char cmdlist[] PROGMEM =
Atm_command no longer work. The v parameter in callback is always 0xFFFF
I am hoping that the person most familiar with the inner workings of this promising Automaton can post the best way for the following two sets of code to be able to run properly within Automaton. It is so a bunch of coin operated washing machines in the USA, & around the world no longer waste unnecessary water & electricity. At this point, I am not seeing any examples within Automaton of how to specifically do what these two sets of code accomplishes. Based on my research, both of these sets of code avoids problematic types of polling &/or delays. Also these two sets of codes have been tested to be 100% reliable within smaller snippets of "spaghetti code" avoiding any Finite State Machine Library completely. At this point, I seem to have a pretty good handle of Automaton's onPress & longPress (for timely 0v 'pullup' digitalReads), & timed led on & offs (to drive pertinent 5V relay coils properly resistors etc.).
The below shows "serial com gathering & displaying" & "holding the accumulating credits" variable until a momentary button is pushed:
// Arduino mainstream 20,4 lcd I2C properly declared & held in the needed scope etc.
int b;
int credits;
if (mySerial.available() >= 0) {
b = mySerial.read();
if (b != 255 || 0) {
credits = credits + b;
lcd.setCursor(16, 3);
lcd.print(credits * .25);
lcd.setCursor(4,0);
lcd.print(credits);
The following "time countdown" continues to run until a trigger or another STATE turns the lcd countdown off & possibly turns it back on a few times with new starting points by just Automaton just seeing updated pertinent variables. The seconds do not have to display if it helps the coding. There needs to be a provision within Automaton so some of these variable can have their values changed based on a specific trigger or a specific state change.
Arduino mainstream 20,4 lcd I2C properly declared & held in the needed scope etc.
int hours, minutes, seconds;
long counter, mytime, interval = 0;
long preInterval = 180000;
long pP = 120000;
long interval = (preInterval-pP);
timeLeft (interval);
void timeLeft (long interval){
lcd.setCursor(0, 3);
lcd.print("Est Time Left: ");
if (millis() > mytime)
mytime = millis() + interval;
counter = (mytime - millis()) / 1000;
hours = counter / 3600;
//counter -= (hours * 3600);
minutes = counter / 60;
counter -= (minutes * 60);
seconds = counter;
if (minutes < 10) {
lcd.print("0");
lcd.print(minutes); }
else
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) {
lcd.print("0");
lcd.print(seconds); }
else
lcd.print(seconds);
This Automaton assistance could potentially help a great number of coin op washing machine owners worldwide & their customers who would otherwise throw out still good very costly stainless steel washing machines unnecesarily. I am looking forward to sharing the complete code on tinkerspy's github upon successful completion & other sites as an example of how Automaton could really shine in a real world extra worthwhile application.
Just a note that Atm_button's AUTO conflicts with the very popular MySensors library.
I end up manually changing Atm_button's AUTO to something else in order to compile my sketches. Which of course, I end up doing each time I update Automaton… :) Maybe there is a better solution on my end, but if so, I'm not familiar with it.
Thank you for your work! Even though I only use a small part of Automaton, it has made my code so much more concise and understandable!
Hi,
I tried my first automaton project and compiled the following sample code
#include <Automaton.h>
Atm_timer timer;
void timer_callback( int idx, int v, int up ) {}
void setup() {
timer.begin(1000)
.repeat(ATM_COUNTER_OFF)
.onTimer(timer_callback)
.start();
}
void loop() {
automaton.run();
}
Unfortunately, I get the following error during compilation:
...Atm_controller.cpp: In member function 'bool Atm_controller::eval_one(atm_connector&)':
...Atm_controller.cpp:47:10: error: 'connector' cannot appear in a constant-expression
case connector.REL_EQ:
(And a lot more of similar errors)
What is the reason of this error ?
Thanks,
John
Hello!
Wonder if you could upgrade Atm_fade to fade between arbitrary values [X1..X2](X1, X2 both bytes), not only in full range [0..255]?
TIA,
--Vladimir
Hello!
I was analysing Atm_button to learn from it (I'm not a real programmer).
There is declaration in header file:
enum { BTN_PASS4 = -2, BTN_PASS3 = -3, BTN_PASS2 = -2, BTN_PASS1 = -1, BTN_RELEASE = 0, BTN_PRESS1 = 1, BTN_PRESS2 = 2, BTN_PRESS3 = 3, BTN_PRESS4 = 4 };
I think there should be BTN_PASS4 = -4
?
Tinkerspy &/or other astute programmer,
In my project I am not seeing an outer need for putting args or parameters with my custom wasco machine in my final sketch. I am doing it based on my humble attempts to follow your creation of your two custom machine examples blink_modular, & trafficlight.
The reason why I am asking is because I am looking for ways to not have something publicly declared to save on scarce dynamic memory.
Can you elaborate on some situations where the following lines in my cpp & h files can be improved upon to help dynamic memory. I am using only 57% of the prog mem whereas I am using 90% of the dynamic memory at this very close to final point. That even with commenting out the "wasco.trace( Serial );" & the other interacting machines' serial monitor trace also.
In .h file under public:
Atm_wasco& begin(int pin4, int pin5, int pin6, int pin7, int pin9, int pin10, int pin11, int pin12, int pin14, int pin15, int pin16);
In .h file under private:
int pin4, pin5, pin6, pin7, pin9, pin10, pin11, pin12, pin14, pin15, pin16;
In .cpp file:
Atm_wasco& Atm_wasco::begin(int pin4, int pin5, int pin6, int pin7, int pin9, int pin10, int pin11, int pin12, int pin14, int pin15, int pin16) {
// clang-format off
Machine::begin( state_table, ELSE );
this->pin4 = pin4;
this->pin5 = pin5;
this->pin6 = pin6;
this->pin7 = pin7;
this->pin9 = pin9;
this->pin10 = pin10;
this->pin11 = pin11;
this->pin12 = pin12;
this->pin14 = pin14;
this->pin15 = pin15;
this->pin16 = pin16;
timer.set(3000);
pinMode (pin4, OUTPUT);
pinMode (pin5, OUTPUT);
pinMode (pin6, OUTPUT);
pinMode (pin15, OUTPUT);
pinMode (pin16, OUTPUT);
pinMode (pin7, INPUT_PULLUP);
pinMode (pin9, INPUT_PULLUP);
pinMode (pin10, INPUT_PULLUP);
pinMode (pin11, INPUT_PULLUP);
pinMode (pin12, INPUT_PULLUP);
pinMode (pin14, INPUT_PULLUP);
return *this;
If anyone can provide helpful suggestions along the line of correcting what is not needed Automaton-Wise to be declared public &/or if "int" should be changed to "byte" or whatever ... I & others would appreciate it.
Mike
https://travis-ci.org/MysteriaLV/boxes/builds/224787770
Maybe Atm_player.* shouldn't be included at all when #ifdef _VARIANT_ARDUINO_DUE_X_
?
P.S. Love your lib. Real life saver. Thanks.
Hi Tinkerspy,
Got my relay node with blinking indicator LED working pretty well, but still encountering this weird bug...
Seem to have the same problem on TinyMachines and Machines.
Created a sketch without any of my custom machines to test it out, running on an Arduino UNO.
The toggles work, machine seems to be functioning, but that one state() call within the button callback doesn't return anything.
Any ideas what could be causing this behaviour?
#include <Automaton.h>
#include <Atm_button.h>
#include <Atm_led.h>
Atm_led led1, led2;
Atm_button btn1, btn2;
Factory factory;
void btn_change( int press, int idx )
{
if ( press ) {
if(idx == 1)
{
// this doesnt work
Serial.println(led1.state());
led1.toggle( led1.IDLE, led1.START );
}
if(idx == 2)
{
// this does??
Serial.println(led2.state());
led2.toggle( led2.IDLE, led2.START );
}
}
}
void setup() {
Serial.begin(9600);
// does not matter how the onPress is defined..
btn1.begin( 1 ).onPress(btn_change, 1);
btn2.begin( 2 ).onPress(btn_change, 2);
led1.begin( 5 ).blink( 20 ).state( led1.START );
led2.begin( 6 ).blink( 10 ).state( led2.START );
// works here
Serial.println(led1.state());
Serial.println(led2.state());
factory.add(btn1).add(btn2).add(led1).add(led2);
}
void loop() {
factory.cycle();
}
Thanks again, hope we can figure it out!
I would like to use an led-machine (only EVT_ON and EVT_OFF needed) with 16 pins controlled through mcp23017. I'm using Adafruit-MCP23017 library:
#include "Adafruit_MCP23017.h"
Adafruit_MCP23017 mcp1;
// in setup:
mcp1.begin( 1 );
for ( int i=0; i<=15;i++ ){
mcp1.pinMode( i, OUTPUT );
}
// somewhere in loop:
mcp1.digitalWrite( 6, HIGH);
I'm thinking about building an own machine derived from led-machine to handle different way of initializing and writing to pins. But before I start: is there a better way to achieve this?
gTom
The encoder machine needs to implement a debounce. When used with mechanical rotary encoders an arbitrary number of events are fired when the encoder is rotated. In my hardware implementation I have a high quality Bourns encoder and capacitive filters on the switches, but still receive 2 to 6 events for each incremental encoder position. In the previous version of the software (before using Automaton) the encoder triggers interrupts and a simple software debounce is implemented. This works very well. The current implementation of Atm_encoder cannot be made to work with this encoder.
Hi,
I have a machine with a state named POWER. In private-part of my machine class definition I added a timer:
Atm_timer timer;
Within the ENT_POWER part of this machine actions I want to start this timer that fires every 1000 ms:
timer.begin(1000).repeat(-1).onTimer( timer_callback ).start();
My callback function is:
void timer_callback( int idx, int v, int up ) {
Serial.print("callback: ");
Serial.println(up); // just counting
}
in EXT_POWER I stop() my timer later on. This works fine.
But now I want to have access to one of my machines public attributes within my callback. How can I achieve that? Is there any possibility to send more arguments (eg. this->value) to my callback?
I think this problem is not about Automaton itself but perhaps anyone knows a working solution?
gTom
This could be a question or a bug report, it could also be an inconsistency between the code and the documentation. The wiki documentation for the led machines onFinish method, describes the event parameter to be EVT_BLINK if no value is given. The following example exists:
led1.begin( 4 ).blink( 500, 500, 3 ).onFinish( led2 );
led2.begin( 4 ).blink( 50, 50, 10 );
led1.trigger( led1.EVT_BLINK );
But looking at the code, and trying it out on an "Adafruit Feather 32u4 basic", I noticed that the default value is commented out and the above example won't work. I had to define the event or it just stopped after the first led.
led1.begin( 4 ).blink( 500, 500, 3 ).onFinish( led2, led1.EVT_BLINK);
led2.begin( 4 ).blink( 50, 50, 10 );
led1.trigger( led1.EVT_BLINK );
Is this the intended behavior? I compile my code on Linux using gcc-avr version 4.9.2
int light1=12000;
int light2=16000;
int light3=20000;
int dark1=12000;
int dark2=8000;
int dark3=4000;
void setup () {
led1.begin( 2 ).blink( light1 ).pause( dark1 ); // Setup
led2.begin( 3 ).blink( light2).pause( dark2 );
led3.begin( 4 ).blink( light3 ).pause( dark3 );
factory.add (led1 ).add( led2 ).add( led3 ); // Add to a factory // Start blinking
led1.msgWrite( led1.MSG_BLINK );
led2.msgWrite( led2.MSG_BLINK );
led3.msgWrite( led3.MSG_BLINK );
}
void loop(){
factory.cycle();
}
In the above program, I contain a function to change the value of all light and dark variables. However, the factory.cycle() still using the initialized value to maintain the blinking process.(e.g. 12000,16000....)
I want to ask is that a normal situation?
What can I do if I want to change value inside blink and pause due to different action?
Apologies if this is not the place to post this, but I've been caught up on this for quite some time:
Hi Tinkerspy & hopefully others,
Anyone confused by this post can get clarification at Tinkerspy's YouTube at:
https://www.youtube.com/watch?v=vKqEeH4OvqU
Directly below is a quote from Automaton's Introduction Mix & match
"You can combine Automaton's event driven code with regular Arduino code as long as you make sure that the automaton.run() method is regularly called to update the running state machines."
Below is hopefully a clear enough explanation of why brainstorming on proper mixing could be very helpful & complement the Automaton Framework to be more versatile & useful.
Attached is a simplified (scaled down to bare necessities) zip folder of a sketch with the needed custom machine that compiles but appears to need a "regular Arduino multiple if statement with results going to State rows in a custom machine" made as a key function to be run as the coin credit total current inserted amounts reach the different thresholds. That in turn goes to one of four holding states that waits for a relatively simple momentary button press (digital input) OV to an active low pin. Then one of the 4 different relatively simple sequences can proceed similar to the TrafficLight example. It will also along with a way to have a countdown timer display & be updated (once or twice if not too hard) with a new starting number. Initially, the countdown & non countdown displays will go to the serial monitor which I should have no trouble at that time putting it on our 20 x 4 backlit LCD. Like I said we are not concerned about the time countdown or the lcd at all at this time. Like Tinkerspy stated ... First things first!
The "baby step" approach at this stage as recommended by Tinkerspy does compile but I am not seeing the functionality ability in the docs of going from the sketch to changing to the appropriate STATE based on a range of the number of the credit (v) variable just by the more limited Automaton Controller Methods. The Automaton Controller method appears to be locked into the more primitive + - > < etc without the ability to to do the c++ " || (or) etc." on a somewhat simple ... albeit temporarily changing credit variable. A more robust "if function" is needed o prevent non starts for the customer using the coin op who accidentally overpays mistaking the higher denomination coin for the lower value coin.
f1_ctl.begin()
.IF( acceptor, '-', 6 ) //This function is perfect for keeping the green ring led on until
.led(16 ); // it is turned off by one of the 4 sequence's start.
f2_ctl.begin()
.IF( acceptor, '=', 8 ) //This is the first of 4 functions that need to allow for
.led(13 ); // credit (v) 7 or 8 , 9, 10 or 11. and last function 12, or 13 or 14 or 15
} // so if the customer accidentally puts in a dollar coin instead of a quarter
// the proper sequence will still start. ALSO INSTEAD WE NEED TO FIND A WAY
void loop() { // to instead of .led we have to have it go to a specific sequence state.
automaton.run();
Thanks Mike
Tinkerspy,
I am asking for help just in case the following is possible within Automaton. If it does allow ... it appears to be the best most straightforward way & possibly the only way to address the "when" part of the need to get my program using Automaton back in its original state ready to accept coins for the next laundromat customer to again do its temporary timely relay activations, & display messages at the exact times needed.
In the Sketch the following function I put in the global area:
void(* resetFunc) (void) = 0; //declare reset function @ address 0
This means I need to get the following to work once when my program finally progresses to its final state (the when factor):
resetFunc();
Being a bit naive ... using the timer machine's trigger ability of "onFinish (mach, mach.EVT_FIN)" ... I put the following in the .cpp in the "action switch case code" to run similar to this:
case ENT_FINAL:
resetFunc();
I am thinking based on the error declaring & referencing message this generated ... I need to not declare this in the global area of the sketch but somehow someway in both the .cpp & the .h files of my main Automaton custom machine that has been proving to do the rest of its "when to act" abilities so far in a very flawless way. I did make an honest effort to put code in the .cpp & .h file but I just am not far enough along skill wise. It would be great to see an example of how to do this nice reset. I do not need any hardware resets ... based on my testing. Just the ablity to control the "when" of this software reset.
I got the idea that this could work based on the step 2 option in the following link:
http://www.instructables.com/id/two-ways-to-reset-arduino-in-software/step2/using-just-software/
The following is another link with a way to get back to the original software starting point same as immediately after loading the ready to test &/or use program to the UNO. Both seem to work during testing if the "when" of execution of the reset was not crucial. Some of my testing yielded continuous repeating of the reset if put outside of the custom acceptor state machine callback.
http://anilarduino.blogspot.com/2014/12/resetting-arduino.html
Thanks
Mike
I merged the development branch to master today. There are a lot of changes in Automaton many thanks to user feedback.
The most important ones:
Automaton has evolved in the direction of a reactive system, which means changes in the system propagate automatically like changes in a spreadsheet. A model that is very well suited to building responsive user interfaces on the Arduino platform.
I have strived to make Automaton a library that allows coding at any level you desire. Use the bundled machines like lego blocks or build your own to automate your specific needs. Build generic state machines and share them with others.
Is you have questions or feedback, please add them in this issue as a comment.
Rgdz, Tinkerspy
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.