Skip to content

Commit

Permalink
emulation on host: Add full UART driver emulation. (esp8266#5785)
Browse files Browse the repository at this point in the history
* emulation on host: Add full UART driver emulation.

  This PR replaces the high level Serial mock with a more complete
  UART driver. This way the HardwareSerial works without any
  modifications. Additionally the driver supports UART0 and UART1
  at the same time (UART0 is directed to stdout and UART1 writes
  to stderr). RX is implemented by switching the terminal into raw
  non-blocking mode and injecting each key-press directly into the
  FIFO of UART0. A new command line switch -c was added to ignore
  CTRL-C and send it via serial as well. The decumentation was
  updated accordingly.

  Reading and setting of GPIOs does only write to stderr, when compiled
  with D=1. But this is subject to be replaced with a proper GPIO emu
  anyway. Reading from GPIO0 now returns 1 instead of 0 because this is
  most likely a low active input.

* Fixed unused variable.

* Remove unused functions, as long as there are no debug macros using them.

* Move user_interface.cc from MOCK_CPP_FILES_EMU to MOCK_CPP_FILES.

* Move optimistic_yield() from user_interface.cpp to Arduino.cpp

* Remove atexit() weak declaration (fixes segfault when calling atexit).

* Improve resetting of terminal after program exit, convert \r to \r\n.

* Show error, when STDOUT is not a TTY, minor code cleanung.
  • Loading branch information
everslick authored and d-a-v committed Feb 22, 2019
1 parent 53f51b5 commit ef44211
Show file tree
Hide file tree
Showing 10 changed files with 545 additions and 159 deletions.
10 changes: 5 additions & 5 deletions tests/host/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ CORE_CPP_FILES := $(addprefix $(CORE_PATH)/,\
libb64/cencode.cpp \
libb64/cdecode.cpp \
Schedule.cpp \
HardwareSerial.cpp \
)

CORE_C_FILES := $(addprefix $(CORE_PATH)/,\
Expand All @@ -74,7 +75,7 @@ MOCK_CPP_FILES_COMMON := $(addprefix common/,\
Arduino.cpp \
spiffs_mock.cpp \
WMath.cpp \
MockSerial.cpp \
MockUART.cpp \
MockTools.cpp \
MocklwIP.cpp \
)
Expand All @@ -95,7 +96,6 @@ MOCK_C_FILES := $(addprefix common/,\
noniso.c \
)


INC_PATHS += $(addprefix -I, \
. \
common \
Expand Down Expand Up @@ -231,7 +231,7 @@ ARDUINO_LIBS := \
WiFiServerSecureBearSSL.cpp \
BearSSLHelpers.cpp \
CertStoreBearSSL.cpp \
) \
)

OPT_ARDUINO_LIBS ?= $(addprefix ../../libraries/,\
$(addprefix ESP8266WebServer/src/,\
Expand All @@ -251,7 +251,7 @@ OPT_ARDUINO_LIBS ?= $(addprefix ../../libraries/,\
DNSServer/src/DNSServer.cpp \
ESP8266AVRISP/src/ESP8266AVRISP.cpp \
ESP8266HTTPClient/src/ESP8266HTTPClient.cpp \
) \
)

MOCK_ARDUINO_LIBS := $(addprefix common/,\
ClientContextSocket.cpp \
Expand All @@ -263,7 +263,7 @@ MOCK_ARDUINO_LIBS := $(addprefix common/,\
MockEsp.cpp \
MockEEPROM.cpp \
MockSPI.cpp \
) \
)

CPP_SOURCES_CORE_EMU = \
$(MOCK_CPP_FILES_EMU) \
Expand Down
12 changes: 11 additions & 1 deletion tests/host/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ WiFiClient+WiFiServer/ClientContext and WifiUdp/UdpContext using socket
posix API. Further work will optionally propose native lwIP library
instead.

Serial UART0 and UART1 are emulated via stdin/stdout/stderr. Therefor
stdin is connected to UART0(RX) and stdout is connected to UART0(TX).
UART1(TX) writes to stderr. Reading from stdin happens in non-blocking
raw mode, that means each character is directly injected into the UART
FIFO without any buffering in the console. The command line switch -c
can be used to stop the emulation from intersepting CTRL-C (SIGINT).

How to compile and run a sketch
-------------------------------

Expand Down Expand Up @@ -73,7 +80,10 @@ Options are available:
-h
-i eth0 bind servers to this interface (WIP)
-l bind Multicast to the above interface (WIP)
-f no throttle (possibly 100%CPU)
-c ignore CTRL-C (send it over serial device)
-f no throttle (possibly 100%CPU)
-S spiffs size in KBytes (default: 1024)
(negative value will force mismatched size)

TODO
----
Expand Down
5 changes: 5 additions & 0 deletions tests/host/common/Arduino.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ extern "C" void yield()
{
}

extern "C" void optimistic_yield (uint32_t interval_us)
{
usleep(interval_us);
}

extern "C" void esp_yield()
{
}
Expand Down
2 changes: 0 additions & 2 deletions tests/host/common/Arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,6 @@ extern "C" {
void init(void);
void initVariant(void);

int atexit(void (*func)()) __attribute__((weak));

void pinMode(uint8_t pin, uint8_t mode);
void digitalWrite(uint8_t pin, uint8_t val);
int digitalRead(uint8_t pin);
Expand Down
77 changes: 69 additions & 8 deletions tests/host/common/ArduinoMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,58 @@
#include <user_interface.h> // wifi_get_ip_info()

#include <signal.h>
#include <unistd.h> // usleep
#include <unistd.h>
#include <getopt.h>
#include <termios.h>
#include <stdarg.h>
#include <stdio.h>

bool user_exit = false;
const char* host_interface = nullptr;
size_t spiffs_kb = 1024;
bool ignore_sigint = false;
bool restore_tty = false;

#define STDIN STDIN_FILENO

static struct termios initial_settings;

static int mock_start_uart(void)
{
struct termios settings;

if (!isatty(STDIN)) return 0;
if (tcgetattr(STDIN, &initial_settings) < 0) return -1;
settings = initial_settings;
settings.c_lflag &= ~(ignore_sigint ? ISIG : 0);
settings.c_lflag &= ~(ECHO | ICANON);
settings.c_iflag &= ~(ICRNL | INLCR | ISTRIP | IXON);
settings.c_oflag |= (ONLCR);
settings.c_cc[VMIN] = 0;
settings.c_cc[VTIME] = 0;
if (tcsetattr(STDIN, TCSANOW, &settings) < 0) return -2;
tty_restore = true;
return 0;
}

static int mock_stop_uart(void)
{
if (!restore_tty) return 0;
if (!isatty(STDIN)) {
perror("isatty(STDIN)");
//system("stty sane"); <- same error message "Inappropriate ioctl for device"
return 0;
}
if (tcsetattr(STDIN, TCSANOW, &initial_settings) < 0) return -1;
printf("\e[?25h"); // show cursor
return (0);
}

static uint8_t mock_read_uart(void)
{
uint8_t ch = 0;
return (read(STDIN, &ch, 1) == 1) ? ch : 0;
}

void help (const char* argv0, int exitcode)
{
Expand All @@ -48,9 +94,10 @@ void help (const char* argv0, int exitcode)
" -h\n"
" -i <interface> - use this interface for IP address\n"
" -l - bind tcp/udp servers to interface only (not 0.0.0.0)\n"
" -c - ignore CTRL-C (send it via Serial)\n"
" -f - no throttle (possibly 100%%CPU)\n"
" -S - spiffs size in KBytes (default: %zd)\n"
" (negative value will force mismatched size)\n"
" (negative value will force mismatched size)\n"
, argv0, spiffs_kb);
exit(exitcode);
}
Expand All @@ -60,13 +107,15 @@ static struct option options[] =
{ "help", no_argument, NULL, 'h' },
{ "fast", no_argument, NULL, 'f' },
{ "local", no_argument, NULL, 'l' },
{ "sigint", no_argument, NULL, 'c' },
{ "interface", required_argument, NULL, 'i' },
{ "spiffskb", required_argument, NULL, 'S' },
};

void save ()
void cleanup ()
{
mock_stop_spiffs();
mock_stop_uart();
}

void control_c (int sig)
Expand All @@ -76,7 +125,7 @@ void control_c (int sig)
if (user_exit)
{
fprintf(stderr, MOCK "stuck, killing\n");
save();
cleanup();
exit(1);
}
user_exit = true;
Expand All @@ -90,7 +139,7 @@ int main (int argc, char* const argv [])

for (;;)
{
int n = getopt_long(argc, argv, "hlfi:S:", options, NULL);
int n = getopt_long(argc, argv, "hlcfi:S:", options, NULL);
if (n < 0)
break;
switch (n)
Expand All @@ -104,6 +153,9 @@ int main (int argc, char* const argv [])
case 'l':
global_ipv4_netfmt = NO_GLOBAL_BINDING;
break;
case 'c':
ignore_sigint = true;
break;
case 'f':
fast = true;
break;
Expand All @@ -128,16 +180,25 @@ int main (int argc, char* const argv [])
// setup global global_ipv4_netfmt
wifi_get_ip_info(0, nullptr);

// set stdin to non blocking mode
mock_start_uart();

// install exit handler in case Esp.restart() is called
atexit(cleanup);

setup();
while (!user_exit)
{
uint8_t data = mock_read_uart();

if (data)
uart_new_data(UART0, data);
if (!fast)
usleep(10000); // not 100% cpu
usleep(1000); // not 100% cpu, ~1000 loops per second
loop();
check_incoming_udp();
}

save();
cleanup();

return 0;
}
14 changes: 13 additions & 1 deletion tests/host/common/HostWiring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,23 @@ void pinMode (uint8_t pin, uint8_t mode)
case WAKEUP_PULLDOWN: m="WAKEUP_PULLDOWN"; break;
default: m="(special)";
}
#ifdef DEBUG_ESP_CORE
fprintf(stderr, MOCK "gpio%d: mode='%s'\n", pin, m);
#endif
}

void digitalWrite(uint8_t pin, uint8_t val)
{
#ifdef DEBUG_ESP_CORE
fprintf(stderr, MOCK "digitalWrite(pin=%d val=%d)\n", pin, val);
#endif
}

void analogWrite(uint8_t pin, int val)
{
#ifdef DEBUG_ESP_CORE
fprintf(stderr, MOCK "analogWrite(pin=%d, val=%d\n", pin, val);
#endif
}

int analogRead(uint8_t pin)
Expand All @@ -67,11 +73,17 @@ int analogRead(uint8_t pin)

void analogWriteRange(uint32_t range)
{
#ifdef DEBUG_ESP_CORE
fprintf(stderr, MOCK "analogWriteRange(range=%d)\n", range);
#endif
}

int digitalRead(uint8_t pin)
{
#ifdef DEBUG_ESP_CORE
fprintf(stderr, MOCK "digitalRead(%d)\n", pin);
return 0;
#endif

// pin 0 is most likely a low active input
return pin ? 0 : 1;
}
Loading

0 comments on commit ef44211

Please sign in to comment.