Add Lua scripting feature

Add support for running Lua scripts that can manipulate the tty control
lines. Script is activated automatically on connect or manually via in
session key command.

The Lua scripting feature opens up for many posibilities in the future
such as adding expect like functionality to easily and programatically
interact with the connected device.
This commit is contained in:
Martin Lund 2024-04-01 15:37:25 +02:00
parent 6fee8514f4
commit 0becfa3274
12 changed files with 590 additions and 72 deletions

View file

@ -29,6 +29,7 @@ color = bold
rs-485 = disable
response-wait = disable
alert = none
script-run = always
# Sub-configurations
@ -63,3 +64,10 @@ device = /dev/ttyUSB0
rs-485 = enable
rs-485-config = RTS_ON_SEND=1,RTS_AFTER_SEND=1,RTS_DELAY_BEFORE_SEND=60,RTS_DELAY_AFTER_SEND=80,RX_DURING_TX
color = 13
[esp32]
device = /dev/ttyUSB0
color = 14
script = high(DTR); low(RTS); msleep(100); low(DTR); high(RTS); msleep(100); low(RTS)
script-run = always

View file

@ -0,0 +1,7 @@
high(DTR)
low(RTS)
msleep(100)
low(DTR)
high(RTS)
msleep(100)
low(RTS)

View file

@ -103,7 +103,7 @@ Enable local echo.
Enable line timestamp.
.TP
.BR " \-\-timestamp-format \fI<format>
.BR " \-\-timestamp\-format \fI<format>
Set timestamp format to any of the following timestamp formats:
.RS
@ -137,12 +137,12 @@ tio_DEVICE_YYYY-MM-DDTHH:MM:SS.log.
The filename can be manually set using the \-\-log-file option.
.TP
.BR " \-\-log-file \fI<filename>
.BR " \-\-log\-file \fI<filename>
Set log filename.
.TP
.BR " \-\-log-append
.BR " \-\-log\-append
Append to log file.
@ -228,7 +228,7 @@ At present there is a hardcoded limit of 16 clients connected at one time.
.RE
.TP
.BR \-r ", " \-\-response-wait
.BR \-r ", " \-\-response\-wait
Wait for line response then quit. A line is considered any string terminated
with a NL character. If no line is received tio will quit after response
@ -279,6 +279,23 @@ will sound the bell twice or blink twice on disconnect.
Default value is "none".
.TP
.BR "\-\-script \fI<string>
Run script from string.
.TP
.BR "\-\-script\-file \fI<filename>
Run script from file with filename.
.TP
.BR "\-\-script\-run once|always|never"
Run script on connect once, always, or never.
Default value is "always".
.TP
.BR \-v ", " \-\-version
@ -317,6 +334,8 @@ Toggle MSB to LSB bit order
Pulse serial port line
.IP "\fBctrl-t q"
Quit
.IP "\fBctrl-t r"
Run script
.IP "\fBctrl-t s"
Show TX/RX statistics
.IP "\fBctrl-t t"
@ -341,6 +360,26 @@ Bytes can be sent in this mode by typing the \fBtwo-character hexadecimal\fR
representation of the value, e.g.: to send \fI0xA\fR you must type \fI0a\fR or
\fI0A\fR.
.SH "SCRIPT API"
.PP
Tio suppots Lua scripting for manipulating the tty device. In addition to the
Lua API tio makes the following functions available:
.TP 6n
.IP "\fBhigh(line)"
Set tty line high.
.IP "\fBlow(line)"
Set tty line low.
.IP "\fBtoggle(line)"
Toggle the tty line.
.IP "\fBsleep(seconds)"
Sleep for seconds.
.IP "\fBmsleep(ms)"
Sleep for miliseconds.
.TP 0n
Note: Line can be any of DTR, RTS, CTS, DSR, CD, RI
.SH "CONFIGURATION FILE"
.PP
Options can be set via configuration file using the INI format. \fBtio\fR uses
@ -428,6 +467,12 @@ Enable RS-485 mode
Set RS-485 configuration
.IP "\fBalert"
Set alert action on connect/disconnect
.IP "\fBscript"
Run script from string
.IP "\fBscript-file"
Run script from file
.IP "\fBscript-run"
Run script on connect.
.SH "CONFIGURATION FILE EXAMPLES"
@ -582,6 +627,12 @@ 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:
$ tio --script "high(DTR); low(RTS); msleep(100); toggle(DTR)" --script-run once /dev/ttyUSB0
.SH "WEBSITE"
.PP
Visit https://tio.github.io

View file

@ -37,6 +37,9 @@ _tio()
--rs-485-config \
--alert \
--mute \
--script \
--script-file \
--script-run \
-v --version \
-h --help"
@ -151,6 +154,18 @@ _tio()
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
--script)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
--script-file)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
--script-run)
COMPREPLY=( $(compgen -W "once always never" -- ${cur}) )
return 0
;;
-v | --version)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0

View file

@ -60,6 +60,9 @@ struct config_t
char *log_filename;
char *socket;
char *map;
char *script;
char *script_filename;
bool script_run;
};
static struct config_t c;
@ -297,6 +300,20 @@ static int data_handler(void *user, const char *section, const char *name,
{
// Do nothing
}
else if (!strcmp(name, "script"))
{
asprintf(&c.script, "%s", value);
option.script = c.script;
}
else if (!strcmp(name, "script-file"))
{
asprintf(&c.script_filename, "%s", value);
option.script_filename = c.script_filename;
}
else if (!strcmp(name, "script-run"))
{
option.script_run = script_run_option_parse(value);
}
else
{
tio_warning_printf("Unknown option '%s' in configuration file, ignored", name);

View file

@ -18,14 +18,27 @@ tio_sources = [
'rs485.c',
'timestamp.c',
'alert.c',
'xymodem.c'
'xymodem.c',
'script.c'
]
foreach name: ['lua-5.4', 'lua-5.3', 'lua-5.2', 'lua-5.1', 'lua']
lua_dep = dependency(name, version: '>=5.1', required: false)
if lua_dep.found()
break
endif
endforeach
if not lua_dep.found()
error('Lua could not be found!')
endif
tio_dep = [
dependency('threads', required: true),
dependency('inih', required: true,
fallback : ['libinih', 'inih_dep'],
default_options: ['default_library=static', 'distro_install=false'])
default_options: ['default_library=static', 'distro_install=false']),
lua_dep
]
tio_c_args = ['-Wno-unused-result']

View file

@ -39,6 +39,7 @@
#include "timestamp.h"
#include "alert.h"
#include "log.h"
#include "script.h"
enum opt_t
{
@ -54,6 +55,9 @@ enum opt_t
OPT_ALERT,
OPT_COMPLETE_SUB_CONFIGS,
OPT_MUTE,
OPT_SCRIPT,
OPT_SCRIPT_FILE,
OPT_SCRIPT_RUN,
};
/* Default options */
@ -96,6 +100,9 @@ struct option_t option =
.rs485_delay_rts_after_send = -1,
.alert = ALERT_NONE,
.complete_sub_configs = false,
.script = NULL,
.script_filename = NULL,
.script_run = SCRIPT_RUN_ALWAYS,
};
void print_help(char *argv[])
@ -134,6 +141,9 @@ void print_help(char *argv[])
printf(" --rs-485-config <config> Set RS-485 configuration\n");
printf(" --alert bell|blink|none Alert on connect/disconnect (default: none)\n");
printf(" --mute Mute tio\n");
printf(" --script <string> Run script from string\n");
printf(" --script-file <filename> Run script from file\n");
printf(" --script-run once|always|never Run script on connect (default: always)\n");
printf(" -v, --version Display version\n");
printf(" -h, --help Display help\n");
printf("\n");
@ -202,6 +212,27 @@ void line_pulse_duration_option_parse(const char *arg)
free(buffer);
}
enum script_run_t script_run_option_parse(const char *arg)
{
if (strcmp("once", arg) == 0)
{
return SCRIPT_RUN_ONCE;
}
else if (strcmp("always", arg) == 0)
{
return SCRIPT_RUN_ALWAYS;
}
else if (strcmp("never", arg) == 0)
{
return SCRIPT_RUN_NEVER;
}
else
{
tio_error_printf("Invalid script run option");
exit(EXIT_FAILURE);
}
}
void options_print()
{
tio_printf(" Device: %s", option.tty_device);
@ -276,6 +307,9 @@ void options_parse(int argc, char *argv[])
{"rs-485-config", required_argument, 0, OPT_RS485_CONFIG },
{"alert", required_argument, 0, OPT_ALERT },
{"mute", no_argument, 0, OPT_MUTE },
{"script", required_argument, 0, OPT_SCRIPT },
{"script-file", required_argument, 0, OPT_SCRIPT_FILE },
{"script-run", required_argument, 0, OPT_SCRIPT_RUN },
{"version", no_argument, 0, 'v' },
{"help", no_argument, 0, 'h' },
{"complete-sub-configs", no_argument, 0, OPT_COMPLETE_SUB_CONFIGS},
@ -439,6 +473,18 @@ void options_parse(int argc, char *argv[])
option.mute = true;
break;
case OPT_SCRIPT:
option.script = optarg;
break;
case OPT_SCRIPT_FILE:
option.script_filename = optarg;
break;
case OPT_SCRIPT_RUN:
option.script_run = script_run_option_parse(optarg);
break;
case 'v':
printf("tio v%s\n", VERSION);
exit(EXIT_SUCCESS);

View file

@ -26,6 +26,7 @@
#include <limits.h>
#include <termios.h>
#include <sys/param.h>
#include "script.h"
#include "timestamp.h"
#include "alert.h"
@ -69,6 +70,9 @@ struct option_t
int32_t rs485_delay_rts_after_send;
enum alert_t alert;
bool complete_sub_configs;
const char *script;
const char *script_filename;
enum script_run_t script_run;
};
extern struct option_t option;
@ -78,3 +82,4 @@ void options_parse(int argc, char *argv[]);
void options_parse_final(int argc, char *argv[]);
void line_pulse_duration_option_parse(const char *arg);
enum script_run_t script_run_option_parse(const char *arg);

280
src/script.c Normal file
View file

@ -0,0 +1,280 @@
/*
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2024 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <lauxlib.h>
#include <lualib.h>
#include <sys/ioctl.h>
#include "print.h"
#include "options.h"
#include "tty.h"
static int serial_fd;
// lua: sleep(seconds)
static int sleep_(lua_State *L)
{
long seconds = lua_tointeger(L, 1);
if (seconds < 0)
{
return 0;
}
tio_printf("Sleeping %ld seconds", seconds);
sleep(seconds);
return 0;
}
// lua: msleep(miliseconds)
static int msleep(lua_State *L)
{
long mseconds = lua_tointeger(L, 1);
long useconds = mseconds * 1000;
if (useconds < 0)
{
return 0;
}
tio_printf("Sleeping %ld ms", mseconds);
usleep(useconds);
return 0;
}
static void script_line_set(int line, bool value)
{
switch (line)
{
case TIOCM_DTR:
tty_line_set(serial_fd, "DTR", line, value);
break;
case TIOCM_RTS:
tty_line_set(serial_fd, "RTS", line, value);
break;
case TIOCM_CTS:
tty_line_set(serial_fd, "CTS", line, value);
break;
case TIOCM_DSR:
tty_line_set(serial_fd, "DSR", line, value);
break;
case TIOCM_CD:
tty_line_set(serial_fd, "CD", line, value);
break;
case TIOCM_RI:
tty_line_set(serial_fd, "RI", line, value);
break;
default:
break;
}
}
static void script_line_toggle(int line)
{
switch (line)
{
case TIOCM_DTR:
tty_line_toggle(serial_fd, "DTR", line);
break;
case TIOCM_RTS:
tty_line_toggle(serial_fd, "RTS", line);
break;
case TIOCM_CTS:
tty_line_toggle(serial_fd, "CTS", line);
break;
case TIOCM_DSR:
tty_line_toggle(serial_fd, "DSR", line);
break;
case TIOCM_CD:
tty_line_toggle(serial_fd, "CD", line);
break;
case TIOCM_RI:
tty_line_toggle(serial_fd, "RI", line);
break;
default:
break;
}
}
// lua: high(line)
static int high(lua_State *L)
{
long line = lua_tointeger(L, 1);
if (line < 0)
{
return 0;
}
script_line_set(line, LINE_HIGH);
return 0;
}
// lua: low(line)
static int low(lua_State *L)
{
long line = lua_tointeger(L, 1);
if (line < 0)
{
return 0;
}
script_line_set(line, LINE_LOW);
return 0;
}
// lua: toggle(line)
static int toggle(lua_State *L)
{
long line = lua_tointeger(L, 1);
if (line < 0)
{
return 0;
}
script_line_toggle(line);
return 0;
}
static void script_buffer_run(lua_State *L, const char *script_buffer)
{
int error;
error = luaL_loadbuffer(L, script_buffer, strlen(script_buffer), "tio") ||
lua_pcall(L, 0, 0, 0);
if (error)
{
tio_warning_printf("%s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
}
}
static const struct luaL_Reg tio_lib[] =
{
{ "sleep", sleep_},
{ "msleep", msleep},
{ "high", high},
{ "low", low},
{ "toggle", toggle},
{NULL, NULL}
};
#if !defined LUA_VERSION_NUM || LUA_VERSION_NUM==501
/*
** Adapted from Lua 5.2.0 (for backwards compatibility)
*/
static void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup)
{
luaL_checkstack(L, nup+1, "too many upvalues");
for (; l->name != NULL; l++) { /* fill the table with given functions */
int i;
lua_pushstring(L, l->name);
for (i = 0; i < nup; i++) /* copy upvalues to the top */
lua_pushvalue(L, -(nup+1));
lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
lua_settable(L, -(nup + 3));
}
lua_pop(L, nup); /* remove upvalues */
}
#endif
int lua_register_tio(lua_State *L)
{
// Register lxi functions
lua_getglobal(L, "_G");
luaL_setfuncs(L, tio_lib, 0);
lua_pop(L, 1);
return 0;
}
void script_file_run(lua_State *L, const char *filename)
{
if (strlen(filename) == 0)
{
tio_warning_printf("Missing script filename\n");
return;
}
if (luaL_dofile(L, filename))
{
tio_warning_printf("%s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
return;
}
}
void script_set_global(lua_State *L, const char *name, long value)
{
lua_pushnumber(L, value);
lua_setglobal(L, name);
}
void script_set_globals(lua_State *L)
{
script_set_global(L, "DTR", TIOCM_DTR);
script_set_global(L, "RTS", TIOCM_RTS);
script_set_global(L, "CTS", TIOCM_CTS);
script_set_global(L, "DSR", TIOCM_DSR);
script_set_global(L, "CD", TIOCM_CD);
script_set_global(L, "RI", TIOCM_RI);
}
void script_run(int fd)
{
lua_State *L;
serial_fd = fd;
L = luaL_newstate();
luaL_openlibs(L);
// Bind tio functions
lua_register_tio(L);
// Initialize globals
script_set_globals(L);
if (option.script_filename != NULL)
{
tio_printf("Running script %s", option.script_filename);
script_file_run(L, option.script_filename);
}
else if (option.script != NULL)
{
tio_printf("Running script");
script_buffer_run(L, option.script);
}
lua_close(L);
}

32
src/script.h Normal file
View file

@ -0,0 +1,32 @@
/*
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2024 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
enum script_run_t
{
SCRIPT_RUN_ONCE,
SCRIPT_RUN_ALWAYS,
SCRIPT_RUN_NEVER,
SCRIPT_RUN_END,
};
void script_run(int fd);

125
src/tty.c
View file

@ -55,6 +55,7 @@
#include "alert.h"
#include "timestamp.h"
#include "misc.h"
#include "script.h"
/* tty device listing configuration */
@ -103,6 +104,7 @@
#define KEY_M 0x6D
#define KEY_P 0x70
#define KEY_Q 0x71
#define KEY_R 0x72
#define KEY_S 0x73
#define KEY_T 0x74
#define KEY_U 0x55
@ -111,12 +113,12 @@
#define KEY_Y 0x79
#define KEY_Z 0x7a
enum line_mode_t
typedef enum
{
LINE_OFF,
LINE_TOGGLE,
LINE_PULSE
};
} tty_line_mode_t;
const char random_array[] =
{
@ -424,19 +426,43 @@ static void output_hex(char c)
}
}
static void toggle_line(const char *line_name, int mask, enum line_mode_t line_mode)
void tty_line_set(int fd, const char *name, int mask, bool value)
{
int state;
if (line_mode == LINE_TOGGLE)
{
// Toggle line
if (ioctl(fd, TIOCMGET, &state) < 0)
{
tio_warning_printf("Could not get line state (%s)", strerror(errno));
return;
}
if (value)
{
state &= ~mask;
tio_printf("Setting %s to HIGH", name);
}
else
{
state |= mask;
tio_printf("Setting %s to LOW", name);
}
if (ioctl(fd, TIOCMSET, &state) < 0)
{
tio_warning_printf("Could not set line state (%s)", strerror(errno));
}
}
void tty_line_toggle(int fd, const char *line_name, int mask)
{
int state;
if (ioctl(fd, TIOCMGET, &state) < 0)
{
tio_warning_printf("Could not get line state (%s)", strerror(errno));
return;
}
if (state & mask)
{
state &= ~mask;
@ -447,44 +473,40 @@ static void toggle_line(const char *line_name, int mask, enum line_mode_t line_m
state |= mask;
tio_printf("Setting %s to LOW", line_name);
}
if (ioctl(fd, TIOCMSET, &state) < 0)
{
tio_warning_printf("Could not set line state (%s)", strerror(errno));
}
} else if (line_mode == LINE_PULSE)
{
int duration = 0;
// Pulse line
toggle_line(line_name, mask, LINE_TOGGLE);
switch (mask)
{
case TIOCM_DTR:
duration = option.dtr_pulse_duration;
break;
case TIOCM_RTS:
duration = option.rts_pulse_duration;
break;
case TIOCM_CTS:
duration = option.cts_pulse_duration;
break;
case TIOCM_DSR:
duration = option.dsr_pulse_duration;
break;
case TIOCM_CD:
duration = option.dcd_pulse_duration;
break;
case TIOCM_RI:
duration = option.ri_pulse_duration;
break;
default:
duration = 0;
break;
}
static void tty_line_pulse(int fd, const char *line_name, int mask, unsigned int duration)
{
tty_line_toggle(fd, line_name, mask);
if (duration > 0)
{
tio_printf("Waiting %d ms", duration);
delay(duration);
}
toggle_line(line_name, mask, LINE_TOGGLE);
tty_line_toggle(fd, line_name, mask);
}
static void tty_line_poke(int fd, const char *name, int mask, tty_line_mode_t mode, unsigned int duration)
{
switch (mode)
{
case LINE_TOGGLE:
tty_line_toggle(fd, name, mask);
break;
case LINE_PULSE:
tty_line_pulse(fd, name, mask, duration);
break;
case LINE_OFF:
break;
}
}
@ -520,7 +542,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
char unused_char;
bool unused_bool;
int state;
static enum line_mode_t line_mode = LINE_OFF;
static tty_line_mode_t line_mode = LINE_OFF;
static char previous_char = 0;
/* Ignore unused arguments */
@ -534,29 +556,29 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
forward = &unused_bool;
}
// Handle tty line toggle and pulse action
if (line_mode)
{
// Handle line toggle number action
*forward = false;
switch (input_char)
{
case KEY_0:
toggle_line("DTR", TIOCM_DTR, line_mode);
tty_line_poke(fd, "DTR", TIOCM_DTR, line_mode, option.dtr_pulse_duration);
break;
case KEY_1:
toggle_line("RTS", TIOCM_RTS, line_mode);
tty_line_poke(fd, "RTS", TIOCM_RTS, line_mode, option.rts_pulse_duration);
break;
case KEY_2:
toggle_line("CTS", TIOCM_CTS, line_mode);
tty_line_poke(fd, "CTS", TIOCM_CTS, line_mode, option.cts_pulse_duration);
break;
case KEY_3:
toggle_line("DSR", TIOCM_DSR, line_mode);
tty_line_poke(fd, "DSR", TIOCM_DSR, line_mode, option.dsr_pulse_duration);
break;
case KEY_4:
toggle_line("DCD", TIOCM_CD, line_mode);
tty_line_poke(fd, "DCD", TIOCM_CD, line_mode, option.dcd_pulse_duration);
break;
case KEY_5:
toggle_line("RI", TIOCM_RI, line_mode);
tty_line_poke(fd, "RI", TIOCM_RI, line_mode, option.ri_pulse_duration);
break;
default:
tio_warning_printf("Invalid line number");
@ -601,6 +623,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
tio_printf(" ctrl-%c m Toggle MSB to LSB bit order", option.prefix_key);
tio_printf(" ctrl-%c p Pulse serial port line", option.prefix_key);
tio_printf(" ctrl-%c q Quit", option.prefix_key);
tio_printf(" ctrl-%c r Run script", option.prefix_key);
tio_printf(" ctrl-%c s Show statistics", option.prefix_key);
tio_printf(" ctrl-%c t Toggle line timestamp mode", option.prefix_key);
tio_printf(" ctrl-%c U Toggle conversion to uppercase on output", option.prefix_key);
@ -726,6 +749,11 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
/* Exit upon ctrl-t q sequence */
exit(EXIT_SUCCESS);
case KEY_R:
/* Run script */
script_run(fd);
break;
case KEY_S:
/* Show tx/rx statistics upon ctrl-t s sequence */
tio_printf("Statistics:");
@ -1383,6 +1411,17 @@ int tty_connect(void)
}
}
/* Manage script activation */
if (option.script_run != SCRIPT_RUN_NEVER)
{
script_run(fd);
if (option.script_run == SCRIPT_RUN_ONCE)
{
option.script_run = SCRIPT_RUN_NEVER;
}
}
/* Input loop */
while (true)
{

View file

@ -23,6 +23,9 @@
#include <stdbool.h>
#define LINE_HIGH true
#define LINE_LOW false
extern bool interactive_mode;
extern bool map_i_nl_cr;
extern bool map_i_cr_nl;
@ -36,3 +39,5 @@ void tty_wait_for_device(void);
void list_serial_devices(void);
void tty_input_thread_create(void);
void tty_input_thread_wait_ready(void);
void tty_line_set(int fd, const char *name, int mask, bool value);
void tty_line_toggle(int fd, const char *name, int mask);