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
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
available:
In addition to the Lua API tio makes the following functions available:
.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)"
Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
.IP "\fBsend(string)"
Send string.
.IP "\fBhigh(line)"
Set tty line high.
.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
.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
.TP
Automatically log in to connected OS:
$ tio --script "expect('password:'); send('my_password\\n')" /dev/ttyUSB0
.SH "WEBSITE"
.PP

View file

@ -20,6 +20,7 @@
*/
#include "config.h"
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@ -87,3 +88,27 @@ bool fs_dir_exists(const char *path)
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_disconnect(void);
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 <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@ -34,7 +35,11 @@
#include "tty.h"
#include "xymodem.h"
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
static int serial_fd;
static char circular_buffer[MAX_BUFFER_SIZE];
static int buffer_size = 0;
// lua: sleep(seconds)
static int sleep_(lua_State *L)
@ -209,6 +214,95 @@ static int send(lua_State *L)
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)
{
int error;
@ -217,7 +311,7 @@ static void script_buffer_run(lua_State *L, const char *script_buffer)
lua_pcall(L, 0, 0, 0);
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 */
}
}
@ -234,6 +328,7 @@ static const struct luaL_Reg tio_lib[] =
{ "config_apply", config_apply},
{ "modem_send", modem_send},
{ "send", send},
{ "expect", expect},
{NULL, NULL}
};
@ -276,7 +371,7 @@ void script_file_run(lua_State *L, const char *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 */
return;
}