Command line needs sending by a "keyboard"

If I send the following command in my windows command processor, it makes an app do the right thing:

curl.exe -d "{"command": "sendCameraCommand","cameras": ["GoPro 2281"],"cameraCommand": "startRecording"}" "http://192.168.0.108:809"

I have set an ESP32 up to behave as a keyboard - this demo works perfectly:

/**
 * This example turns the ESP32 into a Bluetooth LE keyboard that writes the words, presses Enter, presses a media key and then Ctrl+Alt+Delete
 */
#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
  bleKeyboard.begin();
}

void loop() {
  if(bleKeyboard.isConnected()) {
    Serial.println("Sending 'Hello world'...");
    bleKeyboard.print("Hello world");

    delay(1000);

    Serial.println("Sending Enter key...");
    bleKeyboard.write(KEY_RETURN);

    delay(1000);

    Serial.println("Sending Play/Pause media key...");
    bleKeyboard.write(KEY_MEDIA_PLAY_PAUSE);

    delay(1000);

    Serial.println("Sending Ctrl+Alt+Delete...");
    bleKeyboard.press(KEY_LEFT_CTRL);
    bleKeyboard.press(KEY_LEFT_ALT);
    bleKeyboard.press(KEY_DELETE);
    delay(100);
    bleKeyboard.releaseAll();
  }

  Serial.println("Waiting 5 seconds...");
  delay(5000);
}

I have tried to modify the bleKeyboard.print("Hello world"); to have my long command line curl.exe . . . . . in place of the Hello world. Then I get the following error:

C:\Users\Gerald\Dropbox\A.  PROJECTS\GoPro\ESP32 WROOM\GDAttempt00\GDAttempt01.ino\GDAttempt01.ino.ino:17:38: error: stray '\' in program
     bleKeyboard.print("curl.exe -d "{\"command\": \"sendCameraCommand\",\"cameras\": [\"GoPro 2281\"],\"cameraCommand\": \"startRecording\"}" "http://192.168.0.108:809"");
                                      ^
C:\Users\Gerald\Dropbox\A.  PROJECTS\GoPro\ESP32 WROOM\GDAttempt00\GDAttempt01.ino\GDAttempt01.ino.ino:17:169: error: missing terminating " character
     bleKeyboard.print("curl.exe -d "{\"command\": \"sendCameraCommand\",\"cameras\": [\"GoPro 2281\"],\"cameraCommand\": \"startRecording\"}" "http://192.168.0.108:809"");
                                                                                                                                                                         ^~~
C:\Users\Gerald\Dropbox\A.  PROJECTS\GoPro\ESP32 WROOM\GDAttempt00\GDAttempt01.ino\GDAttempt01.ino.ino: In function 'void loop()':
C:\Users\Gerald\Dropbox\A.  PROJECTS\GoPro\ESP32 WROOM\GDAttempt00\GDAttempt01.ino\GDAttempt01.ino.ino:17:37: error: expected ')' before '{' token
     bleKeyboard.print("curl.exe -d "{\"command\": \"sendCameraCommand\",\"cameras\": [\"GoPro 2281\"],\"cameraCommand\": \"startRecording\"}" "http://192.168.0.108:809"");
                      ~              ^
                                     )
C:\Users\Gerald\Dropbox\A.  PROJECTS\GoPro\ESP32 WROOM\GDAttempt00\GDAttempt01.ino\GDAttempt01.ino.ino:41:1: error: expected '}' at end of input
 }
 ^
C:\Users\Gerald\Dropbox\A.  PROJECTS\GoPro\ESP32 WROOM\GDAttempt00\GDAttempt01.ino\GDAttempt01.ino.ino:14:13: note: to match this '{'
 void loop() {
             ^

exit status 1

Compilation error: stray '\' in program

Sorry , I am a beginner. How can I insert my long command line into the program so that the "keyboard" writes it. (I want to to swap between 2 commands every 5 seconds - the words start and stop being the only difference)

Please post the line(s) of code.

From the error messages, it just looks like you have failed to escape every quote mark and maybe a few others.

Post the entire command line in a <CODE/> block, as you would have typed it.

Post the entire attempt to get this as one quoted string (character array or constant). In a <CODE/> block

bleKeyboard.print("curl.exe -d "{\"command... 

The first backslash is without the quotation. Essentially all quotes but the first and last have to be escaped.

bleKeyboard.print (      "curl.exe -d "                   {\"command... 

That first "part" is complete, the backslash makes no sense outside a quote.

Not to add mud to the water, but when or if you start wanting that string to be adjustable by code, keep sprintf() in mind. Still need to escape quote marks.

HTH

Oh, welcome to the forum.

a7

Are you using a windows computer?

If yes the whole thing can be done with an Autohotkey-Script which would eliminate the need for a microcontroller acting as the keyboard

@gerald-d - you can also try a raw string literal, viz:

const char *hey = R"(this is "the bomb"" yeah \\)";

at least since C++ version something, I can't test to see if it works now in the Arduino world.

const char *hey = R"(              this is "the bomb"" yeah \\                )";

That one would have all those spaces, just wanted to show the syntax better.

THX for making me look into something I knew existed.

Old school will work once you get it right.

a7

I am trying to start and stop 3 GoPro cameras. Therefore there will only be 6 variations/permutations of that command line. I derived the line from here. Json converted to single line.

This is what caused the errors:

/**
 * This example turns the ESP32 into a Bluetooth LE keyboard that writes the words, presses Enter, presses a media key and then Ctrl+Alt+Delete
 */
#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
  bleKeyboard.begin();
}

void loop() {
  if(bleKeyboard.isConnected()) {
    Serial.println("Sending 'Hello world'...");
    bleKeyboard.print("curl.exe -d "{\"command\": \"sendCameraCommand\",\"cameras\": [\"GoPro 2281\"],\"cameraCommand\": \"startRecording\"}" "http://192.168.0.108:809"");

    delay(1000);

    Serial.println("Sending Enter key...");
    bleKeyboard.write(KEY_RETURN);

    delay(1000);

    Serial.println("Sending Play/Pause media key...");
    bleKeyboard.write(KEY_MEDIA_PLAY_PAUSE);

    delay(1000);

    Serial.println("Sending Ctrl+Alt+Delete...");
    bleKeyboard.press(KEY_LEFT_CTRL);
    bleKeyboard.press(KEY_LEFT_ALT);
    bleKeyboard.press(KEY_DELETE);
    delay(100);
    bleKeyboard.releaseAll();
  }

  Serial.println("Waiting 5 seconds...");
  delay(5000);
}

for Stefan; I am testing at a windows computer, but eventually it will be on Raspberry Pi3.

(I am trying to automate the making of videos like this https://www.youtube.com/watch?v=c5ApZpFzdh0 )

Please post the command line as you would type it. In a code block.

Simply put, any quote marks within the quoted string have to be escaped. You only did some of them, I think, but until you post what you are trying to say, it's hard to,tell,what you have, and have not, seen or overlooked.

a7

You may try this:

/*
  Forum: https://forum.arduino.cc/t/command-line-needs-sending-by-a-keyboard/1244534/6
  Wokwi: https://wokwi.com/projects/394411520212640769

*/

char command[] = "curl.exe -d \"{\"command\": \"sendCameraCommand\",\"cameras\": [\"GoPro 2281\"],\"cameraCommand\": \"startRecording\"}\" \"http://192.168.0.108:809\"";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println(command);
}

void loop() {
  delay(100);
}

You can test it on Wokwi: LongCommandString - Wokwi ESP32, STM32, Arduino Simulator

I just implemented what @alto777 already posted; escape every " that is a part of the string by a backslash ... Only the first and the last one are used to frame the string (if my assumption is correct).

Good luck!
ec2021

P.S.: This is a screenshot from Wokwi's Serial:

Please check if this is exactly what you want as your command!

1 Like

I trashed my minor objection after looking into curl. Yikes!

a7

Did you get "curly hair" now? :wink:

1 Like

Haha, luck shouldn't have to enter into it. Anyway, nice demo, gotta love the wokwi.

Now @gerald-d should show us where the variable parts of what is now one constant string are, so sprintf() coukd be used.

As an alternative, just make the complete command lines all six as constants, use an array of character constants.

I'd write one to show you but can't whilst moving and working through the tiny window…

a7

Although @gerald-d did not yet show us the variable parts I have implemented your suggestion as an example:

Sketch:

/*
  Forum: https://forum.arduino.cc/t/command-line-needs-sending-by-a-keyboard/1244534/6
  Wokwi: https://wokwi.com/projects/394412450192637953

*/

const byte noOfCams = 6;

char command[] = "curl.exe -d \"{\"command\": \"sendCameraCommand\",\"cameras\": [\"GoPro %s\"],\"cameraCommand\": \"startRecording\"}\" \"http://192.168.0.%s\"\0";

const char camNo[noOfCams][8] {
  "2281",
  "2282",
  "2283",
  "2284",
  "2285",
  "2286"
};

const char camIP[noOfCams][8] {
  "108:809",
  "208:809",
  "308:809",
  "408:809",
  "508:809",
  "608:809",
};

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  printCamCmds();
}

void loop() {
  delay(100);
}


void printCamCmds(){
  char buffer[160];
  for (int i = 0;i< noOfCams;i++){
    sprintf(buffer,command,camNo[i],camIP[i]);
    Serial.println(buffer);
  }
}

@gerald-d : Here some explanations:

char command[]  =  // Should contain all(!) data that are identical for each camera to save memory space
const char camNo[noOfCams] // Holds the different numbers of the cameras 
const char camIP[noOfCams][8]  // Holds the differences of the camera IPs (may be even less e.g. the last 3 entries?)
char buffer[160]; // Must be large enough to hold the final command, otherwise the controller may crash
sprintf(buffer,command,camNo[i],camIP[i]); // Copies camNo and camIP to those places in the command string where you find a string placeholder  %s 

Now its your turn ...
ec2021

See the yellow marked characters in the Wokwi screenshot to identify the differences:

This may not be the changes you need but it should not be too difficult to apply them.

1 Like

Nice. One changes only, a small matter but something I've been trying to make myself do

Say

const char camNo[][8] {... 

and let the compiler count

const byte noOfCams = sizeof camNo / sizeof *camNo;

which line of code would come after the array declaration.

Also, parallel arrays like these can be reformed into one array of struct variables, I don't usually bother until it starts to get out of hand. It's a tradeoff of a kind. For this kinda thing, I like the parallel arrays, for me it is easier to read in the declartions and use.

a7

@gerald-d if you can tell us without then needing to kill us, what are you up to with six goPro cameras automatically all over the place?

Inquiring minds want to know.

a7

Thanks for an amazing response. Unfortunately a lot is Greek to me. I am away from my desk for a while and will consider it all carefully when I get back.

3 gopros in car, front and sides. The each need start and stop commands.

Suggest you maybe read my 2 posts above carefully again.

Just for the fun of it a version with structs and automatic computation of the array size ... :wink:

/*
  Forum: https://forum.arduino.cc/t/command-line-needs-sending-by-a-keyboard/1244534/6
  Wokwi: https://wokwi.com/projects/394418839211631617

*/

struct camDataType{
    char Name[8];
    char PartialIP[8];
};

const char command[] = "curl.exe -d \"{\"command\": \"sendCameraCommand\",\"cameras\": [\"GoPro %s\"],\"cameraCommand\": \"startRecording\"}\" \"http://192.168.0.%s\"\0";

const camDataType cam[] {
  {"2281","108:809"},
  {"2282","208:809"},
  {"2283","308:809"},
  {"2284","408:809"},
  {"2285","508:809"},
  {"2286","608:809"},
};

const byte noOfCams = sizeof(cam)/sizeof(cam[0]);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  printCamCmds();
}

void loop() {
  delay(100);
}


void printCamCmds(){
  char buffer[160];
  for (int i = 0;i< noOfCams;i++){
    sprintf(buffer,command,cam[i].Name,cam[i].PartialIP);
    Serial.println(buffer);
  }
}

See also on Wokwi: https://wokwi.com/projects/394418839211631617

1 Like

So not "off-topic" but off-C++:

After some research and trial and error and asking in the AutoHotkey-forum

Here is a small script which works with Autohotkey v2 (in short AHK-v2)

Attention! There are fundamental differencides between AHK-v1 and AHK-v2

So the script below works only with AHK-v2

You can download it from here https://www.autohotkey.com/

There are a few things that are important to consider

If you want to use mutliple parameters with double-quotationmarks like in your case
you have to put a single-quotation mark at the most left and the most right to make AHK accept the whole thing

#Requires AutoHotkey v2+
;script for AHK v2 does NOT work with AHK v1
#SingleInstance Force

SetTimer(MyTimedAction, 5000)


MyTimedAction()
{ 
  Run 'C:\Windows\SysWOW64\curl.exe -d "{"command": "sendCameraCommand","cameras": ["GoPro 2281"],"cameraCommand": "startRecording"}" "http://192.168.0.108:809" '
  Run 'C:\Windows\SysWOW64\curl.exe -d "{"command": "sendCameraCommand","cameras": ["GoPro 2282"],"cameraCommand": "startRecording"}" "http://192.168.0.208:809" '
  Run 'C:\Windows\SysWOW64\curl.exe -d "{"command": "sendCameraCommand","cameras": ["GoPro 2283"],"cameraCommand": "startRecording"}" "http://192.168.0.308:809" '
  Run 'C:\Windows\SysWOW64\curl.exe -d "{"command": "sendCameraCommand","cameras": ["GoPro 2284"],"cameraCommand": "startRecording"}" "http://192.168.0.408:809" '
  Run 'C:\Windows\SysWOW64\curl.exe -d "{"command": "sendCameraCommand","cameras": ["GoPro 2285"],"cameraCommand": "startRecording"}" "http://192.168.0.508:809" '
  Run 'C:\Windows\SysWOW64\curl.exe -d "{"command": "sendCameraCommand","cameras": ["GoPro 2286"],"cameraCommand": "startRecording"}" "http://192.168.0.608:809" '
}

best regards Stefan

There is a little C++ but not much Greek in it :wink:

What's missing from your side is just the information which parts of the command line change from camera to camera.

  • For the example I assumed that it is the camera number and the last two bytes of the IP, e.g. for the first camera {"2281","108:809"}.
  • I inserted two placeholders in the command line that are filled with the changing data by the sprintf function. The rest of the command line stays as it has been defined.

%s is a placeholder for a string. This placeholder is replaced here by the appropriate cam name:

[\"GoPro %s\"],

So for cam name "1234" the resulting string would be

[\"GoPro 1234\"],

Here %s is replaced by the partial IP

"http://192.168.0.%s\"";

For e.g. PartialIP = "111:222" the result is

"http://192.168.0.111:222\"";

If you have less to change then modify the command line to hold everything that is common to all cams and insert %s exactly and only where different data are required. Then modify the data in the array to provide exactly these data.

The function sprintf expects variables in the same order (left to right) as the placeholders are inserted in the format string (which is called "command" in the examples).

For further explanation refer to:

https://help.campbellsci.com/crbasic/cr1000x/Content/Instructions/sprintf.htm

Hope that this is sufficient information ... :slight_smile:

Have fun!
ec2021

I am a relentless less ink is better person. Saw it this way around here a few ago, adopted it:

const byte noOfCams = sizeof cam / sizeof *cam;

We read them carefully enough to solve your problem.

I didn't see any mention of a car, or where the cameras would be or why you need six or three or any cameras or anything.

All absolutely irrelevant, just abject curiosity.

a7

1 Like

Just had some spare round brackets in my PC and wanted to get rid of them ... :wink:

I knew that one can use sizeof without brackets but did not see "sizeof *array" instead of sizeof(array[0]) before until now. Thanks for the hint!

Of course many ways to express that all can work.

The one that boils my goat, however, is when I see

&someArray[0]

instead of

someArray

Both are pointers to (the address of) the first element, both the correct type for that element.

One of the features I like about C is its strong and consistent relationship between pointers and arrays.

a7