just the emulator shell broken out

pull/46/head
tarxvf 2021-07-04 15:17:25 -04:00 zatwierdzone przez Silvano Seva
rodzic efb549731c
commit 45228e226e
11 zmienionych plików z 673 dodań i 60 usunięć

8
.gitignore vendored
Wyświetl plik

@ -50,6 +50,14 @@ build_*
# gutentags
tags
# subprojects
subprojects/radio_tool
subprojects/codec2
#ignore log files
*.log
#ignore linux openrtx emulator history file
.emulatorsh_history

Wyświetl plik

@ -309,7 +309,7 @@ dm1801_def = def + mk22fn512_def + {'PLATFORM_DM1801': ''}
linux_c_args = ['-DPLATFORM_LINUX']
linux_cpp_args = ['-std=c++14', '-DPLATFORM_LINUX']
linux_l_args = ['-lm']
linux_l_args = ['-lm', '-lreadline']
# Add AddressSanitizer if required
if get_option('asan')

124
meta/leak_check.txt 100644
Wyświetl plik

@ -0,0 +1,124 @@
p
p
p
p
r 12
key 1 2 3 4 5 6 enter
key esc
key 1 2 3 4 5 6 enter
key 4 5 1 8 enter
key 4 5 1 8 enter
key 4 6 9 enter
key esc
key enter
key down down down
key down
key enter
key esc
key down down down down down
key enter
key esc
p
p
key enter
key esc
key 4 5 1 8 enter
key 1 4 6 5 2 enter
p
p
key enter down down down down down enter
key esc
key down down down down
key enter
key esc
key esc
key enter up up enter
key up
key esc
key esc
r 12
r 15
s
vb 7.6
key enter
key esc
s
channel 1
channel 2
ch 3
ch 5
ch 15
key esc
ch 2
key esc
key esc
key esc
key enter
key esc
key esc
key enter
key down down down
key enter
key enter
key down
key enter
key up
key up
key up
key up
key up
key up
key down
key down
key esc
p
p
key esc
key esc
key esc
key up
key esc
key up
key up
key 4 5 1 8 enter enter
p
p
key 1 4 6 5 2 enter 4 5 1 8 enter
p
p
p
p
key enter up up enter
key up
key esc
key down down down down enter
key esc
key down down down down down enter
key esc
key esc
key esc
key esc
key esc
key up
key esc
key up
key up
key 4 5 1 8 enter enter
p
p
key 1 4 6 5 2 enter 4 5 1 8 enter
ready
p
p
p
p
key enter up up enter
key up
key esc
key down down down down enter
key esc
key down down down down down enter
key esc
key esc

Wyświetl plik

@ -0,0 +1,9 @@
sleep 4000
nop SAT menu
key enter DOWN down down down down enter
ready
screenshot sat.bmp
nop GPS menu
key esc DOWN down down down enter
ready
screenshot gps.bmp

Wyświetl plik

@ -0,0 +1,6 @@
sleep 3000
screenshot noptt.bmp
key 1 2 3
ptt
sleep 1000
screenshot ptt.bmp

16
meta/readme 100644
Wyświetl plik

@ -0,0 +1,16 @@
uidev:
meson compile -C build_linux openrtx_linux
cat sat_menu_dev.txt | build_linux/openrtx_linux
convert SAT.bmp -resize 600% SAT.jpg
valgrind:
meson compile -C build_linux openrtx_linux
cat leak_check.txt | valgrind --leak-check=full --log-file=valgrind.log build_linux/openrtx_linux
record:
meson compile -C build_linux openrtx_linux
cat record_demo.txt | build_linux/openrtx_linux

Wyświetl plik

@ -0,0 +1,3 @@
key enter down down down down down enter
sleep 5000
screenshot SAT.bmp

Wyświetl plik

@ -0,0 +1,9 @@
key enter down down down down down enter
sleep 1000
key down enter
sleep 5000
nop sleep 1000
nop key enter
nop sleep 1000
nop screenshot delta.bmp

Wyświetl plik

@ -57,6 +57,100 @@ SDL_Texture *displayTexture; /* SDL rendering surface */
void *frameBuffer; /* Pointer to framebuffer */
bool inProgress; /* Flag to signal when rendering is in progress */
int screenshot_display(const char *filename)
{
//https://stackoverflow.com/a/48176678
//user1902824
//modified to keep renderer and display texture references in the body rather than as a parameter
SDL_Renderer * ren = renderer;
SDL_Texture * tex = displayTexture;
int err = 0;
SDL_Texture *ren_tex;
SDL_Surface *surf;
int st;
int w;
int h;
int format;
void *pixels;
pixels = NULL;
surf = NULL;
ren_tex = NULL;
format = SDL_PIXELFORMAT_RGBA32;
/* Get information about texture we want to save */
st = SDL_QueryTexture(tex, NULL, NULL, &w, &h);
if (st != 0) {
SDL_Log("Failed querying texture: %s\n", SDL_GetError());
err++;
goto cleanup;
}
ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h);
if (!ren_tex) {
SDL_Log("Failed creating render texture: %s\n", SDL_GetError());
err++;
goto cleanup;
}
/*
* Initialize our canvas, then copy texture to a target whose pixel data we
* can access
*/
st = SDL_SetRenderTarget(ren, ren_tex);
if (st != 0) {
SDL_Log("Failed setting render target: %s\n", SDL_GetError());
err++;
goto cleanup;
}
SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00);
SDL_RenderClear(ren);
st = SDL_RenderCopy(ren, tex, NULL, NULL);
if (st != 0) {
SDL_Log("Failed copying texture data: %s\n", SDL_GetError());
err++;
goto cleanup;
}
/* Create buffer to hold texture data and load it */
pixels = malloc(w * h * SDL_BYTESPERPIXEL(format));
if (!pixels) {
SDL_Log("Failed allocating memory\n");
err++;
goto cleanup;
}
st = SDL_RenderReadPixels(ren, NULL, format, pixels, w * SDL_BYTESPERPIXEL(format));
if (st != 0) {
SDL_Log("Failed reading pixel data: %s\n", SDL_GetError());
err++;
goto cleanup;
}
/* Copy pixel data over to surface */
surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(format), w * SDL_BYTESPERPIXEL(format), format);
if (!surf) {
SDL_Log("Failed creating new surface: %s\n", SDL_GetError());
err++;
goto cleanup;
}
/* Save result to an image */
st = SDL_SaveBMP(surf, filename);
if (st != 0) {
SDL_Log("Failed saving image: %s\n", SDL_GetError());
err++;
goto cleanup;
}
SDL_Log("Saved texture as BMP to \"%s\"\n", filename);
cleanup:
SDL_FreeSurface(surf);
free(pixels);
SDL_DestroyTexture(ren_tex);
return err;
}
/**
* @internal
* Internal helper function which fetches pixel at position (x, y) from framebuffer
@ -105,7 +199,8 @@ void display_init()
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
SDL_WINDOW_SHOWN );
//removed RESIZABLE flag so automatic screen recording is a little easier
renderer = SDL_CreateRenderer(window, -1, 0);
SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT);

Wyświetl plik

@ -23,13 +23,17 @@
#include <interfaces/keyboard.h>
#include <SDL2/SDL.h>
extern keyboard_t shellkeyq_get();
void kbd_init()
{
}
keyboard_t kbd_getKeys() {
keyboard_t keys = 0;
SDL_PumpEvents();
//this pulls in emulated keypresses from the command shell
keys |= shellkeyq_get();
const uint8_t *state = SDL_GetKeyboardState(NULL);
if (state[SDL_SCANCODE_0]) keys |= KEY_0;

Wyświetl plik

@ -20,42 +20,261 @@
#include "emulator.h"
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <interfaces/keyboard.h>
#include <SDL2/SDL.h>
#include <readline/readline.h>
#include <readline/history.h>
radio_state Radio_State = {12, 8.2f, 3, 4, 1, false};
int CLIMenu()
{
int choice = 0;
printf("Select the value to change:\n");
printf("1 -> RSSI\n");
printf("2 -> Vbat\n");
printf("3 -> Mic Level\n");
printf("4 -> Volume Level\n");
printf("5 -> Channel selector\n");
printf("6 -> Toggle PTT\n");
printf("7 -> Print current state\n");
printf("8 -> Exit\n");
printf("> ");
do
{
scanf("%d", &choice);
} while (choice < 1 || choice > 8);
printf("\033[1;1H\033[2J");
return choice;
extern int screenshot_display(const char *filename);
typedef int (*_climenu_fn)(void* self, int argc, char ** argv );
typedef struct {
char * name;
char * description;
void * var;
_climenu_fn fn;
} _climenu_option;
enum shell_retvals {
SH_ERR=-1,
SH_CONTINUE=0,
SH_WHAT=1,
SH_EXIT_OK=2,
};
keyboard_t _shellkeyq[25] = {0};
int _skq_cap = 25;
int _skq_head;
int _skq_tail;
int _skq_in;
int _skq_out;
void _dump_skq(){
for( int i = 0; i < _skq_cap; i++){
printf("skq[%d] == %d\n", i, _shellkeyq[i]);
}
}
void shellkeyq_put(keyboard_t keys){
//note - we must allow keys == 0 to be inserted because otherwise a queue full of
// [1,1,1,1,1] is simulating HOLDING 1, and we sometimes (well, often) want
// [1,0,1,0,1,0] to simulate separate keypresses
// this, of course, relies on the kbd_thread getting just one element off the queue
// for every kbd_getKeys().
if( _skq_in > _skq_out + _skq_cap ){
printf("too many keys!\n");
return;
}
_shellkeyq[ _skq_tail ] = keys;
_skq_in++;
_skq_tail = (_skq_tail + 1 ) % _skq_cap;
/*printf("head: %d tail: %d in %d out %d\n", _skq_head, _skq_tail, _skq_in, _skq_out);*/
}
keyboard_t shellkeyq_get(){
if( _skq_in > _skq_out ){
//only if we've fallen behind and there's data in there:
keyboard_t out = _shellkeyq[ _skq_head ];
_shellkeyq[ _skq_head ] = 0;
_skq_out++;
_skq_head = (_skq_head + 1 ) % _skq_cap;
/*printf("head: %d tail: %d in %d out %d\n", _skq_head, _skq_tail, _skq_in, _skq_out);*/
/*_dump_skq();*/
return out;
} else {
return 0; //no keys
}
}
void _test_skq(){
for(int i = 0; i < 257; i++){
shellkeyq_put(i+1);
}
//clear it out now
while( shellkeyq_get() );
}
int shell_ready(
__attribute__((unused)) void * _self,
__attribute__((unused)) int _argc,
__attribute__((unused)) char ** _argv ){
while( _skq_in > _skq_out ){
usleep(10*1000); //sleep until keyboard is caught up
}
return SH_CONTINUE;
}
void updateValue(float *curr_value)
{
printf("Current value: %f\n", *curr_value);
printf("New value: \n");
scanf("%f", curr_value);
keyboard_t keyname2keyboard(char * name){
/*The line noise at the end of this comment is a vim macro for taking the keyboard.h
interface and putting it into the format further below
You can load it into vim register k with "kyy
and run the macro with @k (and then you can repeat a macro register application with @@ )
(substitute k with any register you like)
Once you've got all the names quoted, you can J them all together into a nice block.
_i"ElC",
*/
char * names[] = {
"KEY_0", "KEY_1", "KEY_2", "KEY_3", "KEY_4", "KEY_5", "KEY_6", "KEY_7",
"KEY_8", "KEY_9", "KEY_STAR", "KEY_HASH", "KEY_ENTER", "KEY_ESC", "KEY_UP",
"KEY_DOWN", "KEY_LEFT", "KEY_RIGHT", "KEY_MONI", "KEY_F1", "KEY_F2", "KEY_F3",
"KEY_F4", "KEY_F5", "KEY_F6", "KEY_F7", "KEY_F8", "KEY_F9", "KEY_F10",
};
int numnames = sizeof(names)/sizeof(char*);
for( int i = 0; i < numnames; i++ ){
if( strcasecmp(name,names[i]+4) == 0 ){ //notice case insensitive
/*printf("MATCH with %s\n", names[i]);*/
//+4 to skip the KEY_ on all the names
//so if name == "2", this whole function will return equivalent to KEY_2 cpp define
//and if name=="LEFT", then you get equivalent to KEY_LEFT cpp define
return (1 << i);
//order matters a great deal in names array, has to match
//the bit field generated in interface/keyboard.h
}
}
return 0;
}
void printState()
int pressKey( __attribute__((unused)) void * _self, int _argc, char ** _argv ){
//press a couple keys in sequence
/*_climenu_option * self = (_climenu_option*) _self;*/
printf("Press Keys: [\n");
keyboard_t last = 0;
for( int i = 0; i < _argc; i++ ){
if( _argv[i] != NULL ){
printf("\t%s, \n", _argv[i]);
keyboard_t press = keyname2keyboard( _argv[i] );
if( press == last ){
//otherwise if you send key ENTER DOWN DOWN DOWN DOWN DOWN
//it will just hold DOWN for (5/(kbd_task_hz)) seconds
//so we need to give it a 0 value to get a 'release'
//so the next input is recognized as separate
//we only need to do this if we have two identical keys back to back,
//because keyboard_t will have a zero for this key's
//flag on other keys, which gives us the release we need
shellkeyq_put( 0 );
}
shellkeyq_put(press);
last = press;
}
}
printf("\t]\n");
shell_ready(NULL,0,NULL);
return SH_CONTINUE; // continue
}
int pressMultiKeys( __attribute__((unused)) void * _self, int _argc, char ** _argv ){
//pressMultiKeys allows for key combos by sending all the keys specified in one keyboard_t
/*_climenu_option * self = (_climenu_option*) _self;*/
printf("Press Keys: [\n");
keyboard_t combo = 0;
for( int i = 0; i < _argc; i++ ){
if( _argv[i] != NULL ){
printf("\t%s, \n", _argv[i]);
combo |= keyname2keyboard( _argv[i] );
}
}
shellkeyq_put( combo );
printf("\t]\n");
shell_ready(NULL,0,NULL);
return SH_CONTINUE; // continue
}
//need another function to press them in sequence by loading up a queue for keypress_from_shell to pull from
int template(void * _self, int _argc, char ** _argv ){
_climenu_option * self = (_climenu_option*) _self;
printf( "%s\n\t%s\n" , self->name, self->description);
for( int i = 0; i < _argc; i++ ){
if( _argv[i] != NULL ){
printf("\tArgs:\t%s\n", _argv[i]);
}
}
return SH_CONTINUE; // continue
}
int screenshot(__attribute__((unused)) void * _self, int _argc, char ** _argv ){
char * filename = "screenshot.bmp";
if( _argc && _argv[0] != NULL ){
filename = _argv[0];
}
return screenshot_display(filename) == 0 ? SH_CONTINUE : SH_ERR;
//screenshot_display returns 0 if ok, which is same as SH_CONTINUE
}
/*
int record_start(__attribute__((unused)) void * _self, int _argc, char ** _argv ){
char * filename = "screen.mkv";
if( _argc && _argv[0] != NULL ){
filename = _argv[0];
}
//id="xwininfo -name 'OpenRTX' | grep id: |cut -d ' ' -f 4";
//system("ffmpeg -f x11grab -show_region 1 -region_border 10 -window_id 0x2600016 -i :0.0 out.mkv");
//https://stackoverflow.com/questions/14764873/how-do-i-detect-when-the-contents-of-an-x11-window-have-changed
return SH_ERR;
}
int record_stop(
__attribute__((unused)) void * _self,
__attribute__((unused)) int _argc,
__attribute__((unused)) char ** _argv ){
return SH_ERR;
}
*/
int setFloat(void * _self, int _argc, char ** _argv ){
_climenu_option * self = (_climenu_option*) _self;
if( _argc <= 0 || _argv[0] == NULL ){
printf("%s is %f\n", self->name, *(float*)(self->var));
} else {
sscanf(_argv[0], "%f", (float *)self->var);
printf("%s is %f\n", self->name, *(float*)(self->var));
}
return SH_CONTINUE; // continue
}
int toggleVariable(
__attribute__((unused)) void * _self,
__attribute__((unused)) int _argc,
__attribute__((unused)) char ** _argv ){
_climenu_option * self = (_climenu_option*) _self;
*(int*)self->var = ! *(int*)self->var; //yeah, maybe this got a little out of hand
return SH_CONTINUE; // continue
}
int shell_sleep( __attribute__((unused)) void * _self, int _argc, char ** _argv ){
if( ! _argc || _argv[0] == NULL ){
printf("Provide a number in milliseconds to sleep as an argument\n");
return SH_ERR;
}
useconds_t sleepus = atoi(_argv[0]) * 1000;
usleep(sleepus);
return SH_CONTINUE;
}
int shell_quit(
__attribute__((unused)) void * _self,
__attribute__((unused)) int _argc,
__attribute__((unused)) char ** _argv ){
printf("QUIT: 73!\n");
//could remove history entries here, if we wanted
return SH_EXIT_OK; //normal quit
}
int printState(
__attribute__((unused)) void * _self,
__attribute__((unused)) int _argc,
__attribute__((unused)) char **_argv)
{
printf("\nCurrent state\n");
printf("RSSI : %f\n", Radio_State.RSSI);
@ -64,43 +283,163 @@ void printState()
printf("Volume : %f\n", Radio_State.volumeLevel);
printf("Channel: %f\n", Radio_State.chSelector);
printf("PTT : %s\n\n", Radio_State.PttStatus ? "true" : "false");
return SH_CONTINUE;
}
int shell_nop(
__attribute__((unused)) void * _self,
__attribute__((unused)) int _argc,
__attribute__((unused)) char ** _argv ){
//do nothing! what it says on the tin
return SH_CONTINUE;
}
int shell_help(void * _self, int _argc, char ** _argv );
_climenu_option _options[] = {
/* name/shortcut description var reference, if available method to call */
{"rssi", "Set rssi", (void*)&Radio_State.RSSI, setFloat },
{"vbat", "Set vbat", (void*)&Radio_State.Vbat, setFloat },
{"mic", "Set miclevel", (void*)&Radio_State.micLevel, setFloat },
{"volume", "Set volume", (void*)&Radio_State.volumeLevel,setFloat },
{"channel", "Set channel", (void*)&Radio_State.chSelector, setFloat },
{"ptt", "Toggle PTT", (void*)&Radio_State.PttStatus, toggleVariable },
{"key", "Press keys in sequence (e.g. 'key ENTER DOWN ENTER' will descend through two menus)",
NULL, pressKey },
{"keycombo", "Press a bunch of keys simultaneously ",
NULL, pressMultiKeys },
{"show", "Show current radio state (ptt, rssi, etc)",
NULL, printState },
{"screenshot","[screenshot.bmp] Save screenshot to first arg or screenshot.bmp if none given",
NULL, screenshot },
/*{"record_start", "[screen.mkv] Automatically save a video of the remaining session (or until record_stop is called)",*/
/*NULL, record_start },*/
/*{"record_stop", "Stop the running recording, or no-op if none started",*/
/*NULL, record_stop },*/
{"sleep", "Wait some number of ms", NULL, shell_sleep },
{"help", "Print this help", NULL, shell_help },
{"nop", "Do nothing (useful for comments)",
NULL, shell_nop },
/*{"ready", */
/*"Wait until ready. Currently supports keyboard, so will wait until all keyboard events are processed,"*/
/*"but is already implied by key and keycombo so there's not much direct use for it right now",*/
/*NULL, shell_ready },*/
{"quit", "Quit, close the emulator", NULL, shell_quit },
};
int num_options = (sizeof( _options )/ sizeof(_climenu_option));
int shell_help(
__attribute__((unused)) void * _self,
__attribute__((unused)) int _argc,
__attribute__((unused)) char ** _argv ){
printf("OpenRTX emulator shell\n\n");
for( int i = 0; i < num_options; i++ ){
_climenu_option * o = &_options[i];
printf("%10s -> %s\n", o->name, o->description);
}
return SH_CONTINUE;
}
_climenu_option * findMenuOption(char * tok){
for( int i = 0; i < num_options; i++ ){
_climenu_option * o = &_options[i];
if( strncmp(tok, o->name, strlen(tok)) == 0 ){
//strncmp like this allows for typing shortcuts like just "r" instead of the full "rssi"
//priority for conflicts (like if there's "s" which could mean
// either "show" or "screenshot" )
//is set by ordering in the _options array
return o;
}
}
return NULL;
}
void striptoken(char * token){
for( size_t i = 0; i < strlen(token); i++ ){
if( token[i] == '\n' ){
token[i] = 0;
}
}
}
int process_line(char * line){
char * token = strtok( line, " ");
if( token == NULL ){
return SH_ERR;
}
striptoken(token);
_climenu_option * o = findMenuOption(token);
char * args[12] = {NULL};
int i = 0;
for( i = 0; i < 12; i++ ){
//immediately strtok again since first is a command rest are args
token = strtok(NULL, " ");
if( token == NULL ){
break;
}
striptoken(token);
args[i] = token;
}
if( token != NULL ){
printf("\nGot too many arguments, args truncated \n");
}
if( o != NULL ){
if( o->fn != NULL ){
return o->fn(o, i, args);
} else {
printf("Bad fn for o, check option array for bad data\n");
return SH_ERR;
}
} else {
return SH_WHAT; //not understood
}
}
void *startCLIMenu()
{
int choice;
do
{
choice = CLIMenu();
switch (choice)
{
case VAL_RSSI:
updateValue(&Radio_State.RSSI);
break;
case VAL_BAT:
updateValue(&Radio_State.Vbat);
break;
case VAL_MIC:
updateValue(&Radio_State.micLevel);
break;
case VAL_VOL:
updateValue(&Radio_State.volumeLevel);
break;
case VAL_CH:
updateValue(&Radio_State.chSelector);
break;
case VAL_PTT:
Radio_State.PttStatus = Radio_State.PttStatus ? false : true;
break;
case PRINT_STATE:
printState();
break;
default:
continue;
printf("\n\n");
char * histfile = ".emulatorsh_history";
shell_help(NULL,0,NULL);
/*printf("\n> ");*/
int ret = SH_CONTINUE;
using_history();
read_history(histfile);
do {
/*char * r = fgets(shellbuf, 255, stdin);*/
char * r = readline(">");
if( r == NULL ){
ret = SH_EXIT_OK;
} else if( strlen(r) > 0 ){
add_history(r);
ret = process_line(r);
} else {
ret = SH_CONTINUE;
}
} while (choice != EXIT);
printf("73\n");
switch(ret){
default:
fflush(stdout);
break;
case SH_WHAT:
printf("?\n(type h or help for help)\n");
ret = SH_CONTINUE; //i'd rather just fall through, but the compiler warns. blech.
/*printf("\n>");*/
break;
case SH_CONTINUE:
/*printf("\n>");*/
break;
case SH_EXIT_OK:
//normal quit
break;
case SH_ERR:
//error
printf("Error running that command\n");
ret = SH_CONTINUE;
break;
}
free(r); //free the string allocated by readline
} while ( ret == SH_CONTINUE );
fflush(stdout);
write_history(histfile);
exit(0);
}