UartRemote for Arduino
This library is from a protocol point of view compatible with the MicroPython UartRemote library. It can be used in any Arduino project by adding this whole directory to the Arduino library directory. After importing the UartRemote library, an example can be selected form the examples in the Arduino IDE. Because this library is written in pure C++, it is faster by a factor of approximately 100 compared to the MicroPython implementation.
Differences compared to MicroPython implementation
Because C++ lacks the possibility to generate a function call with a variable number of parameters, a conversion function unpack
was introduced.
A typical definition of a user defined function that will be called upon receiving a command with its accompanying parameters is shown below:
void led(Arguments args) {
int r,g,b,n;
unpack(args,&r,&g,&b,&n);
Serial.printf("LED on: %d, %d, %d, %d\n", r, g, b, n);
uartremote.send_command("ledack","B",0);
}
Here, the user function takes always a single parameters of type Arguments
. To obtain the encoded variables, the unpack
function is called.
The user defined function must always return an acknowledgement. In this case a dummy variable of type byte
is returned.
The following example shows how to return one or more values back.
void add(Arguments args) {
int a,b;
unpack(args,&a,&b);
Serial.printf("sum on: %d, %d\n", a, b);
int c=a+b;
uartremote.send_command("imuack","i",c);
}
In this example the sum of a
and b
is returned as an integer (i
).
API
-
Arguments UartRemote::receive_command(char *cmd);
Waits for an incomming command and return the received command in
cmd
and returns the format and buffer in the structArguments
. The struct member.error
has value 0 when no error occur.
-
void UartRemote::send_command(const char *cmd, const char *format, ...);
Send a command cmd
over the UART where the vaiavle arguments are formatted according to the format
string.
-
Arguments UartRemote::call(const char *cmd, const char *format, ...);
Calls remotely the function specified by cmd
and returns the result in a struct of type Argument
. The struct member .error
equals 1 if an error occured.
-
int UartRemote::receive_execute()
Receives a command and executes the corresponding local function with the parameters as received from the command. This is a combination of receive_command
and command
. Returns 0 if no errors occurred.
-
void UartRemote::add_command(const char *cmd, void (*func)(Arguments));
Adds a function to the list of commands. In the example below, the function tst
is added:
def tst(Arguments a) {
...
}
uartremote.add_command("tst",&tst);
-
void UartRemote::command(const char *cmd, Arguments rcvunpack);
Executes the function by looking up the cmd
string in the internal cmds
array that is filled using the add_command
method and maps function names as sring to the actual function pointers.
-
int UartRemote::available();
Checks whether a character is available in the UART receive buffer. This can be used for a non-blocking implementation of UartRemote in your own loop.
-
void UartRemote::flush();
Flushes the UART receive buffer. This can be used if an error is suspected.
Private methods:
-
Arguments UartRemote::pack(unsigned char *buf, const char *format, ...);
Packs the variatic list of arguments according to the format
string in the buffer buf
.
-
unsigned char UartRemote::readserial1()
Reads a single byte from the UART receive buffer.
Arguments struct
We use a struct Arguments
to store the format string together with the buffer with the unpacked data. The friend unpack
function takes care for the proper unpacking of the buffer according to the format string. The .error
struct member is used for passing error status back.
struct Arguments {
void* buf;
const char* fmt;
int error;
template<typename... Args> friend void unpack(const Arguments& a, Args... args) {
struct_unpack(a.buf,a.fmt, args...);
}
};
Examples
Below are some code snippets showing to use the Arduino side as a master and as a slave with its counter part written in Python.
char cmd[32]; // global temporary storage for command names
UartRemote uartremote;
void setup() {
...
uartremote.add_command("led", &led);
uartremote.add_command("add", &add);
...
}
void loop() {
int error = uartremote.receive_execute();
if (error==1) {
printf("error in receiving command\n");
}
}
With on the Python side the following code:
from uartremote import *
from utime import sleep_ms
ur=UartRemote()
ur.flush()
while True:
ack,s=ur.call('add','2i',1,2)
print("sum = ",s)
sleep_ms(500)
ur.call('led','4i',1,2,3,4)
sleep_ms(500)
The other way round it would look like:
char cmd[32]; // global temporary storage for command names
UartRemote uartremote;
void setup() {
...
}
int i=0;
int s=0;
void loop() {
i+=1;
i%=100;
args=uartremote.call("led","4i",i+1,i+2,i+3,i+4);
if (args.error==0) {
printf("received ledack: %s\n",cmd);
} else { printf("error from call led\n");}
delay(1000);
args=uartremote.call("add","2i",i+1,i+2);
if (args.error==0) {
unpack(args,&s);
printf("Received sumack: %s, sum=%d\n",cmd,s);
} else { printf("error from call sum\n");}
delay(1000);
}
On the Pyhton side we have the following code:
from uartremote import *
ur=UartRemote()
def led(n,r,g,b):
print('led',n,r,g,b)
def add(a,b):
print('adding',a,b)
return(a+b)
ur.add_command(led)
ur.add_command(add,'i')
ur.loop()
Struct library
We use the struct
library with implemenations of pack
and unpack
supporting Python compatible format strings. The code can be found on github.com/svperbeast/struct.