I am having a bit of an issue, I am not sure how to code this.
I have an arduinoMega controlling a mechanism using a finite state machine and I would like to connect it to a computer with could communicate with it via serial using python.
The only command that the computer needs to send to the arduino is a status request and the arduino needs to reply with the current status of the finite state machine.
I simplified my state machine(it turns the LED on and OFF) for the sake of the discussion:
here is my arduino code:
int x = 0;
int y = 0;
int stat = 0;
int S_info;
void setup(void)
{
pinMode(12, OUTPUT); //led
Serial.begin(115200);
Serial.setTimeout(1);
}
void FSM()
{
static enum { one, two } state = one;
switch (state)
{
case one:
digitalWrite(12, LOW);
x=x+1;
stat =1;
if (x ==100)
{
state = two;
x = 0;
}
break;
case two:
digitalWrite(12, HIGH);
y = y +1;
stat = 2;
if (y == 1000)
{
state = one;
y = 0;
}
break;
default:
state = one;
break;
}
}
void loop(void)
{
//run FSM
FSM();
//serial connection
S_info = Serial.readString().toInt();
if (S_info ==1)
{
Serial.print(stat);
}
}
My python code so fare is the following:
# Importing Libraries
import serial
import time
import sys
arduino = serial.Serial(port='/dev/ttyUSB0', baudrate=115200, timeout=.1)
def write_read(cmd):
arduino.write(bytes(cmd, 'utf-8'))
time.sleep(0.05)
data = arduino.readline()
return data
for timer in range(1000):
status = write_read(1)
print(status)
You are repeatedly setting the LED and also repeatedly assigning a value to 'stat'. It's harmless in this case, but futile. The range checks for x and y should not use equality alone as a bounds check because it might be skipped. It works here but it's safer to use "greater than or equal to" to protect against future program changes. Also, you can format your code using ctrl-T in the IDE. I fixed these issues, you only have to ensure the correct starting state for the LED before the state machine runs.
switch (state)
{
case one:
x = x + 1;
if (x >= 100)
{
digitalWrite(12, HIGH);
stat = 2;
state = two;
x = 0;
}
break;
case two:
y = y + 1;
if (y >= 1000)
{
digitalWrite(12, LOW);
stat = 1;
state = one;
y = 0;
}
break;
default:
state = one;
break;
}
One more thing, "one" and "two" surely don't describe what the states do. It is better to give them meaningful names, otherwise you might as well just use integers.
I'll try to put the serial read in an FSM of it`s own.
How would you recommend?
I am thinking something of this (putting pseudocode):
if ( serial.available())
{
S_info = Serial.readString().toInt();
}
It should only go in the if statement if something was sent to the serial bus correct?
@anon57585045
I agree that the state machine is a bit stupid right now, it's because I took my massive state machine and just dummed it down, the x and y will be replaced by limit switches and since I am running steppers, the x and y will store cartesian positions, they are just place holders to simulate running the operation multiple times until I get the to the point I need.
Your FSM should collect characters (if available()) and check for digits. Should non-digits be ignored before the first digit? How many digits should be expected? Is there a definite end-of-number marker? Many questions to be answered and handled in your FSM.
I am not 100% sure of what is happening when a serial message is sent to the arduino while the arduino is doing another operation, will the arduino store it in the buffer until I get to the line to check the serial bus or will it be lost?
the codes that will be received over serial will only be integers, from 1 to 5.
basically, we want to be able to code the following instructions:
1 -> send back status update
2 -> reset system
3 -> debug mode
4 -> ...
5 -> ...
Yes, Serial input is interrupt driven. Nothing will get lost unless the buffer overflows. Serial.available() will tell you how many characters already have been received.
Also your python program is doing “read line”, but your arduino sketch isn’t transmitting an end-of-line character.
It’s not immediately clear how that interacts with the short timeout, but it could be a problem.
The problem is, it's only non blocking if you happen to perform your read after all the bytes in your received message have been received and are waiting in the buffer. That is not realistic. There is no sensible way to ensure those conditions. Normally, serial is so slow and asynchronous that you will find either one, or zero characters waiting there.
there is a library called SafeString which can be installed from the Arduino-IDE library manager.
The name SafeString is program.
You can assign a SafeString as many characters as ever possible. This can never cause an overflow. If your program trys to add more characters to a SafeString than the SafeString can hold these characters are just dropped. You as the user will see the characters are dropped but the code will not crash.
SafeString offers almost the same comfort as the variabletype String but without crash-danger
Hello Doc
You are right.
The SerialEvent sketch is handling the rx of data without blocking.
A timeout handler shall be added for a better performance in real life.
An asynchronous event handler could be designed by using structured arrays with function pointers.
Have a nice day and enjoy programming in C++ and learning.
Дайте миру шанс!
rhetoric question: which code will be used by newcomers?
the code that comes close to their needs combined with easyness to understand how to apply it.
I have written a small tutorial with a demo-code how to use SafeString non-blocking serial input
and then derived from there a code that demonstrates LED switching on/off using serial commands that set states of a fsm
That is what I call a starting point
#include "SafeStringReader.h"
createSafeStringReader(myInputReader, 5, " ,\r\n");
const byte LED_pin = 13;
const byte fsm_idling = 1;
const byte fsm_switchLEDOn = 2;
const byte fsm_waitWithLEDOn = 3;
const byte fsm_SwitchLEDOFF = 4;
byte myStateVar;
unsigned long myWaitTimer;
const unsigned long LedOnTime = 10000;
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - startOfPeriod >= TimePeriod ) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
}
else return false; // actual TimePeriod is NOT yet over
}
void ManageSerialCommands() {
if (myInputReader.read()) { // check if all characters of a command including delimiter are received
if (myInputReader == "ON") {
myStateVar = fsm_switchLEDOn;
}
if (myInputReader == "OFF") {
myStateVar = fsm_SwitchLEDOFF;
}
}
}
void setup() {
pinMode(LED_pin, OUTPUT); // no comment needed because of SELF-explaining name led
Serial.begin(115200);
Serial.println( F("Setup-Start") );
Serial.println( F("enter command ON or OFF to switch LED") );
SafeString::setOutput(Serial); // enable error messages and SafeString.debug() output to be sent to Serial
myInputReader.connect(Serial); // where SafeStringReader will read from in this demo the standard Serial
myInputReader.echoOn(); // echo back all input, by default echo is off
myStateVar = fsm_idling;
}
void FSM() {
switch (myStateVar) {
case fsm_idling:
// just do sit and wait for serial commands
break;
case fsm_switchLEDOn:
digitalWrite(LED_pin, HIGH);
myWaitTimer = millis(); // initialise Timer-variable with actual value of millis()
myStateVar = fsm_waitWithLEDOn;
break;
case fsm_waitWithLEDOn:
if ( TimePeriodIsOver(myWaitTimer, LedOnTime) ) { // check if waitingtime is over
// if number of milliseconds stored in LedOnTime have passed by
Serial.println( F("waiting time over chnage to switching LED off") );
myStateVar = fsm_SwitchLEDOFF;
}
break;
case fsm_SwitchLEDOFF:
digitalWrite(LED_pin, LOW);
Serial.println( F("LED switched off") );
Serial.println( F("enter command ON or OFF to switch LED") );
myStateVar = fsm_idling;
default:
myStateVar = fsm_idling;
break;
}
}
void loop() {
ManageSerialCommands();
FSM();
}