Add lua expect(string)

Add simple expect functionality.

The expect(string) function will wait for input from the tty device and
only return when there is a string match. Regular expressions are
supported.

Example:

script = expect('password:'); send('my_password\n')
This commit is contained in:
Martin Lund 2024-04-12 18:20:27 +02:00
parent 0afae5d3ee
commit fc54df1f22
4 changed files with 133 additions and 7 deletions

View file

@ -375,17 +375,18 @@ Send ctrl-t character
.PP .PP
Tio suppots Lua scripting to easily automate interaction with the tty device. Tio suppots Lua scripting to easily automate interaction with the tty device.
This means that in addition to the Lua API tio makes the following functions In addition to the Lua API tio makes the following functions available:
available:
.TP 6n .TP 6n
.IP "\fBexpect(string)"
Expect string - waits for string to match before continueing. Supports regular expressions. Special characters must be escaped with '\\\\'.
.IP "\fBsend(string)"
Send string.
.IP "\fBmodem_send(file, protocol)" .IP "\fBmodem_send(file, protocol)"
Send file using x/y-modem protocol. Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
.IP "\fBsend(string)"
Send string.
.IP "\fBhigh(line)" .IP "\fBhigh(line)"
Set tty line high. Set tty line high.
.IP "\fBlow(line)" .IP "\fBlow(line)"
@ -660,10 +661,14 @@ Enable RS-485 mode:
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0 $ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
.TP .TP
Script manipulation of DTR and RTS lines upon first connect: Manipulate DTR and RTS lines upon first connect to reset connected microcontroller:
$ tio --script "high(DTR); low(RTS); msleep(100); toggle(DTR)" --script-run once /dev/ttyUSB0 $ tio --script "high(DTR); low(RTS); msleep(100); toggle(DTR)" --script-run once /dev/ttyUSB0
.TP
Automatically log in to connected OS:
$ tio --script "expect('password:'); send('my_password\\n')" /dev/ttyUSB0
.SH "WEBSITE" .SH "WEBSITE"
.PP .PP

View file

@ -20,6 +20,7 @@
*/ */
#include "config.h" #include "config.h"
#include <regex.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@ -87,3 +88,27 @@ bool fs_dir_exists(const char *path)
return true; return true;
} }
bool regex_match(const char *string, const char *pattern)
{
regex_t regex;
int status;
if (regcomp(&regex, pattern, REG_EXTENDED | REG_NOSUB) != 0)
{
// No match
return false;
}
status = regexec(&regex, string, (size_t) 0, NULL, 0);
regfree(&regex);
if (status != 0)
{
// No match
return false;
}
// Match
return true;
}

View file

@ -32,3 +32,4 @@ int ctrl_key_code(unsigned char key);
void alert_connect(void); void alert_connect(void);
void alert_disconnect(void); void alert_disconnect(void);
bool fs_dir_exists(const char *path); bool fs_dir_exists(const char *path);
bool regex_match(const char *string, const char *pattern);

View file

@ -20,6 +20,7 @@
*/ */
#include <errno.h> #include <errno.h>
#include <regex.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@ -34,7 +35,11 @@
#include "tty.h" #include "tty.h"
#include "xymodem.h" #include "xymodem.h"
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
static int serial_fd; static int serial_fd;
static char circular_buffer[MAX_BUFFER_SIZE];
static int buffer_size = 0;
// lua: sleep(seconds) // lua: sleep(seconds)
static int sleep_(lua_State *L) static int sleep_(lua_State *L)
@ -209,6 +214,95 @@ static int send(lua_State *L)
return 1; return 1;
} }
// Function to add a character to the circular buffer
void add_to_buffer(char c)
{
if (buffer_size < MAX_BUFFER_SIZE)
{
circular_buffer[buffer_size++] = c;
}
else
{
// Shift the buffer to accommodate the new character
memmove(circular_buffer, circular_buffer + 1, MAX_BUFFER_SIZE - 1);
circular_buffer[MAX_BUFFER_SIZE - 1] = c;
}
}
// Function to match against the circular buffer using regex
bool match_regex(regex_t *regex)
{
char buffer[MAX_BUFFER_SIZE + 1]; // Temporary buffer for regex matching
memcpy(buffer, circular_buffer, buffer_size);
buffer[buffer_size] = '\0'; // Null-terminate the buffer
// Match against the regex
int ret = regexec(regex, buffer, 0, NULL, 0);
if (!ret)
{
// Match found
return true;
}
else if (ret == REG_NOMATCH)
{
// No match found, do nothing
}
else
{
// Error occurred during matching
tio_error_print("Regex match failed");
}
return false;
}
// lua: expect(string)
static int expect(lua_State *L)
{
const char *string = lua_tostring(L, 1);
regex_t regex;
int ret = 0;
char c;
if (string == NULL)
{
ret = -1;
goto error;
}
// Compile the regular expression
ret = regcomp(&regex, string, REG_EXTENDED);
if (ret)
{
tio_error_print("Could not compile regex");
ret = -1;
goto error;
}
// Main loop to read and match
while (true)
{
ssize_t bytes_read = read(serial_fd, &c, 1);
if (bytes_read > 0)
{
putchar(c);
add_to_buffer(c);
// Match against the entire buffer
if (match_regex(&regex))
{
break;
}
}
}
// Cleanup
regfree(&regex);
error:
lua_pushnumber(L, ret);
return 1;
}
static void script_buffer_run(lua_State *L, const char *script_buffer) static void script_buffer_run(lua_State *L, const char *script_buffer)
{ {
int error; int error;
@ -217,7 +311,7 @@ static void script_buffer_run(lua_State *L, const char *script_buffer)
lua_pcall(L, 0, 0, 0); lua_pcall(L, 0, 0, 0);
if (error) if (error)
{ {
tio_warning_printf("%s\n", lua_tostring(L, -1)); tio_warning_printf("lua: %s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */ lua_pop(L, 1); /* pop error message from the stack */
} }
} }
@ -234,6 +328,7 @@ static const struct luaL_Reg tio_lib[] =
{ "config_apply", config_apply}, { "config_apply", config_apply},
{ "modem_send", modem_send}, { "modem_send", modem_send},
{ "send", send}, { "send", send},
{ "expect", expect},
{NULL, NULL} {NULL, NULL}
}; };
@ -276,7 +371,7 @@ void script_file_run(lua_State *L, const char *filename)
if (luaL_dofile(L, filename)) if (luaL_dofile(L, filename))
{ {
tio_warning_printf("%s\n", lua_tostring(L, -1)); tio_warning_printf("lua: %s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */ lua_pop(L, 1); /* pop error message from the stack */
return; return;
} }