Have terminal bash script act as virtual "button" for Arduino?

I've searched quite a lot for this and tried to piece bits of code together but with no luck so far.

All i'm looking to do is have a command I can run in terminal/bash script on the Mac trigger some code to run on the Arduino (Leonardo)

It doesn't matter what data the Mac sends, I only have one command to run so it doesn't need to look like separate buttons or anything.

So far i've tried

int incomingByte = 0;   // for incoming serial data
void setup() {
    Serial.begin(9600);
}

void loop() {

        // send data only when you receive data:
        if (Serial.available() > 0) {
                // read the incoming byte:
                incomingByte = Serial.read();
//run commands
  }
        }

and then from terminal/script both

echo "1" > /dev/tty.usbmodemHIDPC1 9600

and

screen > /dev/tty.usbmodemHIDPC1 9600

I see the Arduino light flash when I run those two commands in terminal which would seem to suggest that its receiving the data, but nothing runs.

I have stripped out my code that is due to be run by the way for simplicity. I also include keyboard.h and want the Leonardo send keyboard presses.

realdannys:
I see the Arduino light flash when I run those two commands in terminal which would seem to suggest that its receiving the data, but nothing runs.

There's nothing to run in the code you posted.

realdannys:
I have stripped out my code that is due to be run by the way for simplicity. I also include keyboard.h and want the Leonardo send keyboard presses.

Don't add that extra complexity in yet. Start by getting an LED to turn on when the serial data is received.

pert:
There's nothing to run in the code you posted.

Yes I know, that's why I said i've stripped my code out - the code i've got works perfectly - I just want it to be triggered my the terminal on the Mac.

I lined up the braces to make it easier to see at a glance. The baud rate and char instead of int are tips to try. Uno has 2K for heap and stack memory, if you develop good RAM habits from the start you can go far without a crash. BTW, don't use type String on Arduino.

char chr;   // for incoming serial data, type char is signed 8-bit, -128 to 127, ASCII is char codes. Save 1 byte over type int.

void setup() 
{
    Serial.begin(115200);  // set serial monitor to this, it empties the outgoing buffer 12x faster than 9600.
}

void loop() 
{
    // read data only when you receive data:
    if (Serial.available() > 0) 
    {
        // read the next char in the Serial input buffer, might be the last/latest arrived
        chr = Serial.read();
        // run commands
        //     ummmmmmm, what commands?
        Serial.println( chr, HEX );
        // if ( chr == 'D' ) // do thing if key is D
    }
}

Thanks @GoForSmoke

I've used the below code but i've still got the same issue from terminal on the Mac.

If I use

echo "1" > /dev/tty.usbmodemHIDPC1 115200

Then I see the Leonardo LED flash once, but then nothing happens. Also it locks up terminal as if it's in a state of trying to send commands. Is there a terminal command I can add to the bash script that will effectively just ping the Arduino and exit so it's not locking the terminal session up?

Here is my Arduino code now.

#include <Keyboard.h>
char chr;   // for incoming serial data, type char is signed 8-bit, -128 to 127, ASCII is char codes. Save 1 byte over type int.
void setup() {
    Keyboard.begin();
    Serial.begin(115200);  // set serial monitor to this, it empties the outgoing buffer 12x faster than 9600.
}

void loop() 
{
    // read data only when you receive data:
    if (Serial.available() > 0) 
    {
        // read the next char in the Serial input buffer, might be the last/latest arrived
        chr = Serial.read();
        // run commands
        delay(3000); 
  Keyboard.write('S');
  delay(100);
  Keyboard.write('Y');
  delay(100);
  Keyboard.write('S');
  delay(100);
  Keyboard.write('T');
  delay(100);
  Keyboard.write('E');
  delay(100);
  Keyboard.write('M');
  delay(100);
        Serial.println( chr, HEX );
        // if ( chr == 'D' ) // do thing if key is D
    }
}

GoForSmoke:
I lined up the braces to make it easier to see at a glance. The baud rate and char instead of int are tips to try. Uno has 2K for heap and stack memory, if you develop good RAM habits from the start you can go far without a crash. BTW, don't use type String on Arduino.

char chr;   // for incoming serial data, type char is signed 8-bit, -128 to 127, ASCII is char codes. Save 1 byte over type int.

void setup()
{
    Serial.begin(115200);  // set serial monitor to this, it empties the outgoing buffer 12x faster than 9600.
}

void loop()
{
    // read data only when you receive data:
    if (Serial.available() > 0)
    {
        // read the next char in the Serial input buffer, might be the last/latest arrived
        chr = Serial.read();
        // run commands
        //    ummmmmmm, what commands?
        Serial.println( chr, HEX );
        // if ( chr == 'D' ) // do thing if key is D
    }
}

Ah i've cracked triggering it!

It needs to be

echo "1" > /dev/cu.usbmodemHIDPC1 115200

That works and exits in terminal and triggers the command.

Now the only problem as it's in loop is that it keeps writing system over and over. Is there a way I can make it just write the command once and stop? I'm wanting to put this in a script where I have a lot of keyboard commands in just the void setup part which run once, but it seems I need to use loop for listening as it didn't work when I put this code into setup. It's just I only want it to run the code once, not loop forever once the byte is received.

Current code is

#include <Keyboard.h>
char chr;   // for incoming serial data, type char is signed 8-bit, -128 to 127, ASCII is char codes. Save 1 byte over type int.
void setup() {
    Keyboard.begin();
    Serial.begin(115200);  // set serial monitor to this, it empties the outgoing buffer 12x faster than 9600.
}

void loop() 
{
    // read data only when you receive data:
    if (Serial.available() > 0) 
    {
        // read the next char in the Serial input buffer, might be the last/latest arrived
        if (chr = Serial.read() > 0)
        {// run commands
        delay(3000); 
  Keyboard.write('S');
  delay(100);
  Keyboard.write('Y');
  delay(100);
  Keyboard.write('S');
  delay(100);
  Keyboard.write('T');
  delay(100);
  Keyboard.write('E');
  delay(100);
  Keyboard.write('M');
  delay(100);
        Serial.println( chr, HEX );
        // if ( chr == 'D' ) // do thing if key is D
        }
    }
}

GoForSmoke:
I lined up the braces to make it easier to see at a glance. The baud rate and char instead of int are tips to try. Uno has 2K for heap and stack memory, if you develop good RAM habits from the start you can go far without a crash. BTW, don't use type String on Arduino.

char chr;   // for incoming serial data, type char is signed 8-bit, -128 to 127, ASCII is char codes. Save 1 byte over type int.

void setup()
{
   Serial.begin(115200);  // set serial monitor to this, it empties the outgoing buffer 12x faster than 9600.
}

void loop()
{
   // read data only when you receive data:
   if (Serial.available() > 0)
   {
       // read the next char in the Serial input buffer, might be the last/latest arrived
       chr = Serial.read();
       // run commands
       //     ummmmmmm, what commands?
       Serial.println( chr, HEX );
       // if ( chr == 'D' ) // do thing if key is D
   }
}

after it sends the message, read all available chars and it won't send the message until you trigger it again.

while( Serial.available()) Serial.read(); // empties the serial input buffer

Maybe take more time reading the code you have to start seeing how it works and what advantages you can take.

You could code the whole thing in setup() if you really only want 1 time action. Setup is where you put what only runs once.

Also posted at:

If you're going to do that then please be considerate enough to add links to the other places you cross posted. This will let us avoid wasting time due to duplicate effort and also help others who have the same questions and find your post to discover all the relevant information. When you post links please always use the chain links icon on the toolbar to make them clickable.

GoForSmoke:
after it sends the message, read all available chars and it won't send the message until you trigger it again.

while( Serial.available()) Serial.read(); // empties the serial input buffer

Maybe take more time reading the code you have to start seeing how it works and what advantages you can take.

You could code the whole thing in setup() if you really only want 1 time action. Setup is where you put what only runs once.

For me it didn't work at all if I put it in setup, and in loop as soon as I triggered it it kept repeating over and over rather than waiting for another trigger.

At the moment I did

#define EVER (;;)

and at the end of the loop

  Keyboard.write('l');
  delay(100);
        Serial.println( chr, HEX );
        // if ( chr == 'D' ) // do thing if key is D
 for EVER;

Probably not the cleanest way but it does make it run once and allows me to run single run code in setup.

Back after two months. At one point I thought i'd cracked this but now can't for the life of me get it to work properly in my script.

I'm basically looking for the Leonardo to enter all the keyboard presses and then wait for the computer to tell it's done and carry on with the rest of the script.

After putting the above code within a button press it no longer works anymore.

A limited version of my code is as follows

#include <HID-Project.h>
#include <HID-Settings.h>
char chr;   // for incoming serial data, type char is signed 8-bit, -128 to 127, ASCII is char codes. Save 1 byte over type int.
int buttonPin;
int buttonPin2;

void setup() {
  Serial.begin(115200);
  BootKeyboard.begin();
  Keyboard.begin();
  Mouse.begin();
  pinMode(5, OUTPUT);
  buttonPin = 4; //whatever pin your button is plugged into
  buttonPin2 = 6; //whatever pin your button is plugged into
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(buttonPin2, INPUT_PULLUP);
}

void loop() {
unsigned int AnalogValue;
AnalogValue = analogRead(A0);

  //check button pressed, if so enter program condition (inside if statement)
  if(digitalRead(buttonPin) == LOW) //functions based off of button pulling input pin LOW
  {
    Keyboard.print("Button Pressed");
    delay(1000);
    //Various keyboard button presses and commands removed
    Keyboard.press("O");
    delay(1000);
    //At this point I want the Leonardo to sit and wait for the computer to tell it's ready for the next part by sending a signal over serial.
    Keyboard.print("Continuing Code");
    delay(1000);
    while (analogRead(A0) > 180){ // waits for light sensor to get a reading below 180 then carries on with code
      }
     delay (50);
    Bootkeyboard.press("R");

So as you can see, I have a light sensor now too and that sits and waits for the light to reach a certain level before continuing the code, I need to do this now in the script with the computer which will in terminal send some serial data from a bash script by sending using the following line

echo "1" > /dev/cu.usbmodemCHIDGG1

You used this in the one that worked

echo "1" > /dev/cu.usbmodemHIDPC1 115200

and the next in the one that doesn't

echo "1" > /dev/cu.usbmodemCHIDGG1

One of these things is not like the other.

PS : you can do straight terminal IO without using HID.

  • don't have Serial Monitor open
  • get the board com port address from IDE->Tools->Port
  • in Linux I use cu to connect the terminal to the board
    $ cu -l /dev/ttyACM0 -s 115200
  • to get cu use
    $ sudo apt-get install cu
  • in Windoze I've used PuTTY or Hyperterminal.

Always disconnect the terminal before compiling with the IDE, reconnect after the sketch loads.
Terminals get sketch serial output and send chars to board serial input as they are pressed. No need for HID.

The answer I was looking for was

while(Serial.available() == 0){}

You can do this without HID, it would take less effort.

You think of code in terms of blocking to force steps into order. It is what you can do with what you know about code. What it does is ensure that only one thing happens at once while everything else waits.

There is another way that does not block, handles things as they happen. Each step begins by checking if it should run

if ( serial.available > 0 ) { ....
}

if ( buttonState == PRESSED ) { ....
}

if ( digitalRead( sensorPin[ 3 ] ) == LOW ) { ....
}

switch ( processState ) { ....
// where each state has a case to run
}

and none of them blocks then each one gets a chance to run no matter which other is not triggered yet.. unless one must trigger to set the trigger for another either on condition or as part of a sequence.

There are a few techniques to start out with explained in the blogs addressed below. They amount to a non-blocking toolkit good enough to get started writing effective real world code.

GoForSmoke:
You can do this without HID, it would take less effort.

I use HID because that's how the Leonardo is being used as a keyboard running macro scripts on the computer waiting for the computer to tell it when it's finished - so it's connected via USB - this way is simple.

At the moment blocking doesn't matter - the script is full of delays where it needs to be 5 seconds or so before the next key press - at some point if I find I need a pause button a few times then i'll try and get ti re-written so that it'll work with one, but at the minute it works perfectly for me.

realdannys:
I use HID because that's how the Leonardo is being used as a keyboard running macro scripts on the computer waiting for the computer to tell it when it's finished - so it's connected via USB

And you have trouble getting the terminal output?

"Back after two months. At one point I thought i'd cracked this but now can't for the life of me get it to work properly in my script."

Without the HID you can send and read the terminal using Serial. I do it with an Uno and Linux terminal. What Arduino prints goes into console as if I'd typed it and runs. What the console outputs I can read on Arduino but the PC must connect the terminal to the Arduino com port at the correct baud rate.

GoForSmoke:
And you have trouble getting the terminal output?

No. I think you might think i'm' doing something different to what I am.

My bash script runs on the Mac - all I was trying to do was to get the Leonardo to wait for the script to finish running and then carry on with it's button presses. I just needed the Mac to send a signal to the Leonardo that i was done - that's what the "echo "1" is.

The while command does this perfectly for my needs.

The wait-synchronization of that while loop reads serial or is supposed to, hence you don't need HID to do the same that serial print or write does ... and that's keeping execution flow exactly as you have it, blocking step-time control which is a very sure thing, right?

The rest of what I wrote is about taking it from chess level to rts level, it's just a head's up.

GoForSmoke:
The wait-synchronization of that while loop reads serial or is supposed to, hence you don't need HID to do the same that serial print or write does ... and that's keeping execution flow exactly as you have it, blocking step-time control which is a very sure thing, right?

I have no idea what you're saying to be honest.

What that while command does for you is the same whether you use HID to send characters or not.

You can send to a terminal window using regular Serial.print and read terminal output using Serial.read.
You cannot read the terminal using HID so you use Serial to read, why not use Serial to do both and lose the extra unnecessary complication?