Initial template version based of cspemu

modbus
Ondřej Hruška 2 years ago
commit c2e87940f2
  1. 4
      .gitignore
  2. 6
      CMakeLists.txt
  3. 8
      Makefile
  4. 8
      components/common_utils/CMakeLists.txt
  5. 2
      components/common_utils/README.txt
  6. 3
      components/common_utils/component.mk
  7. 75
      components/common_utils/include/common_utils/base16.h
  8. 131
      components/common_utils/include/common_utils/datetime.h
  9. 19
      components/common_utils/include/common_utils/hexdump.h
  10. 80
      components/common_utils/include/common_utils/utils.h
  11. 62
      components/common_utils/src/base16.c
  12. 52
      components/common_utils/src/common_utils.c
  13. 110
      components/common_utils/src/datetime.c
  14. 72
      components/common_utils/src/hexdump.c
  15. 9
      components/dhcp_wd/CMakeLists.txt
  16. 27
      components/dhcp_wd/Kconfig
  17. 5
      components/dhcp_wd/README.txt
  18. 3
      components/dhcp_wd/component.mk
  19. 82
      components/dhcp_wd/include/dhcp_wd.h
  20. 277
      components/dhcp_wd/src/dhcp_wd.c
  21. 16
      components/fileserver/CMakeLists.txt
  22. 2
      components/fileserver/README.txt
  23. 3
      components/fileserver/component.mk
  24. BIN
      components/fileserver/files/embed/favicon.ico
  25. 106
      components/fileserver/files/embed/index.html
  26. 163
      components/fileserver/files/rebuild_file_tables.php
  27. 15
      components/fileserver/files/www_files_enum.c
  28. 13
      components/fileserver/files/www_files_enum.h
  29. 51
      components/fileserver/include/fileserver/embedded_files.h
  30. 216
      components/fileserver/include/fileserver/token_subs.h
  31. 29
      components/fileserver/readme/README.md
  32. 170
      components/fileserver/readme/rebuild_file_tables.php
  33. 22
      components/fileserver/src/embedded_files.c
  34. 580
      components/fileserver/src/token_subs.c
  35. 9
      components/httpd_utils/CMakeLists.txt
  36. 4
      components/httpd_utils/README.txt
  37. 3
      components/httpd_utils/component.mk
  38. 32
      components/httpd_utils/include/httpd_utils/captive.h
  39. 13
      components/httpd_utils/include/httpd_utils/fd_to_ipv4.h
  40. 16
      components/httpd_utils/include/httpd_utils/redirect.h
  41. 55
      components/httpd_utils/include/httpd_utils/session.h
  42. 77
      components/httpd_utils/include/httpd_utils/session_kvmap.h
  43. 100
      components/httpd_utils/include/httpd_utils/session_store.h
  44. 106
      components/httpd_utils/src/captive.c
  45. 42
      components/httpd_utils/src/fd_to_ipv4.c
  46. 20
      components/httpd_utils/src/redirect.c
  47. 181
      components/httpd_utils/src/session_kvmap.c
  48. 220
      components/httpd_utils/src/session_store.c
  49. 41
      components/httpd_utils/src/session_utils.c
  50. 4
      components/ping/CMakeLists.txt
  51. 1
      components/ping/README.txt
  52. 3
      components/ping/component.mk
  53. 58
      components/ping/include/ping.h
  54. 262
      components/ping/src/ping.c
  55. 4
      components/socket_server/CMakeLists.txt
  56. 1
      components/socket_server/README.txt
  57. 3
      components/socket_server/component.mk
  58. 282
      components/socket_server/include/socket_server.h
  59. 1015
      components/socket_server/src/socket_server.c
  60. 18
      components/vconsole/CMakeLists.txt
  61. 54
      components/vconsole/libconsole/.gitignore
  62. 73
      components/vconsole/libconsole/CMakeLists.txt
  63. 1
      components/vconsole/libconsole/LICENSE.txt
  64. 24
      components/vconsole/libconsole/include/console/cmddef.h
  65. 22
      components/vconsole/libconsole/include/console/config.h.in
  66. 362
      components/vconsole/libconsole/include/console/console.h
  67. 196
      components/vconsole/libconsole/include/console/console_io.h
  68. 94
      components/vconsole/libconsole/include/console/prefix_match.h
  69. 213
      components/vconsole/libconsole/include/console/utils.h
  70. 9
      components/vconsole/libconsole/lib/argtable3/CMakeLists.txt
  71. 3
      components/vconsole/libconsole/lib/argtable3/README.txt
  72. 4969
      components/vconsole/libconsole/lib/argtable3/argtable3.c
  73. 306
      components/vconsole/libconsole/lib/argtable3/argtable3.h
  74. 1824
      components/vconsole/libconsole/src/console.c
  75. 42
      components/vconsole/libconsole/src/console_filecap.c
  76. 194
      components/vconsole/libconsole/src/console_io.c
  77. 1129
      components/vconsole/libconsole/src/console_linenoise.c
  78. 248
      components/vconsole/libconsole/src/console_linenoise.h
  79. 196
      components/vconsole/libconsole/src/console_prefix_match.c
  80. 120
      components/vconsole/libconsole/src/console_split_argv.c
  81. 36
      components/vconsole/libconsole/src/console_split_argv.h
  82. 204
      components/vconsole/libconsole/src/console_utils.c
  83. 645
      components/vconsole/libconsole/src/queue.h
  84. 46
      main/CMakeLists.txt
  85. 23
      main/Kconfig.projbuild
  86. 62
      main/app_main.c
  87. 26
      main/application.h
  88. 4
      main/component.mk
  89. 48
      main/console/cmd_common.h
  90. 34
      main/console/commands/cmd_dump.c
  91. 50
      main/console/commands/cmd_factory_reset.c
  92. 38
      main/console/commands/cmd_heap.c
  93. 341
      main/console/commands/cmd_ip.c
  94. 52
      main/console/commands/cmd_pw.c
  95. 43
      main/console/commands/cmd_restart.c
  96. 51
      main/console/commands/cmd_tasks.c
  97. 52
      main/console/commands/cmd_version.c
  98. 360
      main/console/commands/cmd_wifi.c
  99. 418
      main/console/console_ioimpl.c
  100. 77
      main/console/console_ioimpl.h
  101. Some files were not shown because too many files have changed in this diff Show More

4
.gitignore vendored

@ -0,0 +1,4 @@
.idea/
build
cmake-build-*
*.old

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(espnode)

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := hello-world
include $(IDF_PATH)/make/project.mk

@ -0,0 +1,8 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_SRCDIRS
"src")
#set(COMPONENT_REQUIRES)
register_component()

@ -0,0 +1,2 @@
General purpose, mostly platofrm-idependent utilities
that may be used by other components.

@ -0,0 +1,3 @@
COMPONENT_SRCDIRS := src
COMPONENT_ADD_INCLUDEDIRS := include

@ -0,0 +1,75 @@
/*
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
*
* 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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef BASE16_H_
#define BASE16_H_
#include <stdint.h>
#include <string.h>
/**
* Calculate length of base16-encoded data
* @param raw_len Raw data length
* @return Encoded string length (excluding NUL)
*/
static inline size_t base16_encoded_len(size_t raw_len) {
return (2 * raw_len);
}
/**
* Calculate maximum length of base16-decoded string
* @param encoded Encoded string
* @return Maximum length of raw data
*/
static inline size_t base16_decoded_max_len(const char *encoded) {
return ((strlen(encoded) + 1) / 2);
}
/**
* Base16-encode data
*
* The buffer must be the correct length for the encoded string. Use
* something like
*
* char buf[ base16_encoded_len ( len ) + 1 ];
*
* (the +1 is for the terminating NUL) to provide a buffer of the
* correct size.
*
* @param raw Raw data
* @param len Length of raw data
* @param encoded Buffer for encoded string
*/
extern void base16_encode(uint8_t *raw, size_t len, char *encoded);
/**
* Base16-decode data
*
* The buffer must be large enough to contain the decoded data. Use
* something like
*
* char buf[ base16_decoded_max_len ( encoded ) ];
*
* to provide a buffer of the correct size.
* @param encoded Encoded string
* @param raw Raw data
* @return Length of raw data, or negative error
*/
extern int base16_decode(const char *encoded, uint8_t *raw);
#endif /* BASE16_H_ */

@ -0,0 +1,131 @@
/**
* TODO file description
*
* Created on 2019/09/13.
*/
#ifndef CSPEMU_DATETIME_H
#define CSPEMU_DATETIME_H
#include <stdbool.h>
#include <stdint.h>
enum weekday {
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};
_Static_assert(MONDAY==1, "enum weekday numbering Mon");
_Static_assert(SUNDAY==7, "enum weekday numbering Sun");
enum month {
JANUARY = 1,
FEBRUARY,
MARCH,
APRIL,
MAY,
JUNE,
JULY,
AUGUST,
SEPTEMBER,
OCTOBER,
NOVEMBER,
DECEMBER
};
_Static_assert(JANUARY==1, "enum month numbering Jan");
_Static_assert(DECEMBER==12, "enum month numbering Dec");
/** Abbreviated weekday names */
extern const char *DT_WKDAY_NAMES[];
/** Full-length weekday names */
extern const char *DT_WKDAY_NAMES_FULL[];
/** Abbreviated month names */
extern const char *DT_MONTH_NAMES[];
/** Full-length month names */
extern const char *DT_MONTH_NAMES_FULL[];
typedef struct datetime {
uint16_t year;
enum month month;
uint8_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
enum weekday wkday; // 1=monday
} datetime_t;
// Templates for printf
#define DT_FORMAT_DATE "%d/%d/%d"
#define DT_SUBS_DATE(dt) (dt).year, (dt).month, (dt).day
#define DT_FORMAT_TIME "%d:%02d:%02d"
#define DT_SUBS_TIME(dt) (dt).hour, (dt).min, (dt).sec
#define DT_FORMAT_DATE_WK DT_FORMAT_WK " " DT_FORMAT_DATE
#define DT_SUBS_DATE_WK(dt) DT_SUBS_WK(dt), DT_SUBS_DATE(dt)
#define DT_FORMAT_WK "%s"
#define DT_SUBS_WK(dt) DT_WKDAY_NAMES[(dt).wkday]
#define DT_FORMAT_DATE_TIME DT_FORMAT_DATE " " DT_FORMAT_TIME
#define DT_SUBS_DATE_TIME(dt) DT_SUBS_DATE(dt), DT_SUBS_TIME(dt)
#define DT_FORMAT DT_FORMAT_DATE_WK " " DT_FORMAT_TIME
#define DT_SUBS(dt) DT_SUBS_DATE_WK(dt), DT_SUBS_TIME(dt)
// base century for two-digit year conversions
#define DT_CENTURY 2000
// start year for weekday computation
#define DT_START_YEAR 2019
// January 1st weekday of DT_START_YEAR
#define DT_START_WKDAY TUESDAY
// max date supported by 2-digit year RTC counters (it can't check Y%400==0 with only two digits)
#define DT_END_YEAR 2399
typedef union __attribute__((packed)) {
struct __attribute__((packed)) {
uint8_t ones : 4;
uint8_t tens : 4;
};
uint8_t byte;
} bcd_t;
_Static_assert(sizeof(bcd_t) == 1, "Bad bcd_t len");
/** Check if a year is leap */
static inline bool is_leap_year(int year)
{
return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
}
/**
* Check if a datetime could be valid (ignores leap years)
*
* @param[in] dt
* @return basic validations passed
*/
bool datetime_is_valid(const datetime_t *dt);
/**
* Set weekday based on a date in a given datetime
*
* @param[in,out] dt
* @return success
*/
bool datetime_set_weekday(datetime_t *dt);
/**
* Get weekday for given a date
*
* @param year - year number
* @param month - 1-based month number
* @param day - 1-based day number
* @return weekday
*/
enum weekday date_weekday(uint16_t year, enum month month, uint8_t day);
#endif //CSPEMU_DATETIME_H

@ -0,0 +1,19 @@
/**
* @file
* @brief A simple way of dumping memory to a hex output
*
* \addtogroup Hexdump
*
* @{
*/
#include <stdio.h>
#define HEX_DUMP_LINE_BUFF_SIZ 16
extern void hex_dump(FILE * fp,void *src, int len);
extern void hex_dump_buff_line(FILE *fp, int addr_size, unsigned pos, char *line, unsigned len);
/**
* }@
*/

@ -0,0 +1,80 @@
/**
* General purpose, platform agnostic, reusable utils
*/
#ifndef COMMON_UTILS_UTILS_H
#define COMMON_UTILS_UTILS_H
#include <stdbool.h>
#include <stdint.h>
#include "base16.h"
#include "datetime.h"
#include "hexdump.h"
/** Convert a value to BCD struct */
static inline bcd_t num2bcd(uint8_t value)
{
return (bcd_t) {.ones=value % 10, .tens=value / 10};
}
/** Convert unpacked BCD to value */
static inline uint8_t bcd2num(uint8_t tens, uint8_t ones)
{
return tens * 10 + ones;
}
/**
* Append to a buffer.
*
* In case the buffer capacity is reached, it stays unchanged (up to the terminator) and NULL is returned.
*
* @param buf - buffer position to append at; if NULL is given, the function immediately returns NULL.
* @param appended - string to append
* @param pcap - pointer to a capacity variable
* @return the new end of the string (null byte); use as 'buf' for following appends
*/
char *append(char *buf, const char *appended, size_t *pcap);
/**
* Test if a file descriptor is valid (e.g. when cleaning up after a failed select)
*
* @param fd - file descriptor number
* @return is valid
*/
bool fd_is_valid(int fd);
/**
* parse user-provided string as boolean
*
* @param str
* @return 0 false, 1 true, -1 invalid
*/
int parse_boolean_arg(const char *str);
/** Check equality of two strings; returns bool */
#define streq(a, b) (strcmp((const char*)(a), (const char*)(b)) == 0)
/** Check prefix equality of two strings; returns bool */
#define strneq(a, b, n) (strncmp((const char*)(a), (const char*)(b), (n)) == 0)
/** Check if a string starts with a substring; returns bool */
#define strstarts(a, b) strneq((a), (b), (int)strlen((b)))
#ifndef MIN
/** Get min of two numbers */
#define MIN(a, b) ((a) > (b) ? (b) : (a))
#endif
#ifndef MAX
/** Get max of two values */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef STR
#define STR_HELPER(x) #x
/** Stringify a token */
#define STR(x) STR_HELPER(x)
#endif
#endif //COMMON_UTILS_UTILS_H

@ -0,0 +1,62 @@
/*
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
*
* 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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <esp_log.h>
static const char *TAG = "base16";
void base16_encode(uint8_t *raw, size_t len, char *encoded) {
uint8_t *raw_bytes = raw;
char *encoded_bytes = encoded;
size_t remaining = len;
for (; remaining--; encoded_bytes += 2)
snprintf(encoded_bytes, 3, "%02X", *(raw_bytes++));
}
int base16_decode(const char *encoded, uint8_t *raw) {
const char *encoded_bytes = encoded;
uint8_t *raw_bytes = raw;
char buf[3];
char *endp;
size_t len;
while (encoded_bytes[0]) {
if (!encoded_bytes[1]) {
ESP_LOGE(TAG, "Base16-encoded string \"%s\" has invalid length\n",
encoded);
return -22;
}
memcpy(buf, encoded_bytes, 2);
buf[2] = '\0';
*(raw_bytes++) = strtoul(buf, &endp, 16);
if (*endp != '\0') {
ESP_LOGE(TAG,"Base16-encoded string \"%s\" has invalid byte \"%s\"\n",
encoded, buf);
return -22;
}
encoded_bytes += 2;
}
len = (raw_bytes - raw);
return (len);
}

@ -0,0 +1,52 @@
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#include "common_utils/utils.h"
char *append(char *buf, const char *appended, size_t *pcap)
{
char c;
char *buf0 = buf;
size_t cap = *pcap;
if (buf0 == NULL) return NULL;
if (appended == NULL || appended[0] == 0) return buf0;
while (cap > 1 && 0 != (c = *appended++)) {
*buf++ = c;
cap--;
}
if (cap == 0) {
*buf0 = '\0';
return NULL;
}
*pcap = cap;
*buf = 0;
return buf;
}
bool fd_is_valid(int fd)
{
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
int parse_boolean_arg(const char *str)
{
if (0 == strcasecmp(str, "on")) return 1;
if (strstarts(str, "en")) return 1;
if (strstarts(str, "y")) return 1;
if (0 == strcmp(str, "1")) return 1;
if (0 == strcasecmp(str, "a")) return 1;
if (0 == strcasecmp(str, "off")) return 0;
if (0 == strcmp(str, "0")) return 0;
if (strstarts(str, "dis")) return 0;
if (strstarts(str, "n")) return 0;
return -1;
}

@ -0,0 +1,110 @@
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "common_utils/datetime.h"
const char *DT_WKDAY_NAMES[] = {
[MONDAY] = "Mon",
[TUESDAY] = "Tue",
[WEDNESDAY] = "Wed",
[THURSDAY] = "Thu",
[FRIDAY] = "Fri",
[SATURDAY] = "Sat",
[SUNDAY] = "Sun"
};
const char *DT_WKDAY_NAMES_FULL[] = {
[MONDAY] = "Monday",
[TUESDAY] = "Tuesday",
[WEDNESDAY] = "Wednesday",
[THURSDAY] = "Thursday",
[FRIDAY] = "Friday",
[SATURDAY] = "Saturday",
[SUNDAY] = "Sunday"
};
const char *DT_MONTH_NAMES[] = {
[JANUARY] = "Jan",
[FEBRUARY] = "Feb",
[MARCH] = "Mar",
[APRIL] = "Apr",
[MAY] = "May",
[JUNE] = "Jun",
[JULY] = "Jul",
[AUGUST] = "Aug",
[SEPTEMBER] = "Sep",
[OCTOBER] = "Oct",
[NOVEMBER] = "Nov",
[DECEMBER] = "Dec"
};
const char *DT_MONTH_NAMES_FULL[] = {
[JANUARY] = "January",
[FEBRUARY] = "February",
[MARCH] = "March",
[APRIL] = "April",
[MAY] = "May",
[JUNE] = "June",
[JULY] = "July",
[AUGUST] = "August",
[SEPTEMBER] = "September",
[OCTOBER] = "October",
[NOVEMBER] = "November",
[DECEMBER] = "December"
};
static const uint16_t MONTH_LENGTHS[] = { /* 1-based, normal year */
[JANUARY]=31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
_Static_assert(sizeof(MONTH_LENGTHS) / sizeof(uint16_t) == 13, "Months array length");
static const uint16_t MONTH_YEARDAYS[] = { /* 1-based */
[JANUARY]=0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 // // days until 1st of month
};
_Static_assert(sizeof(MONTH_YEARDAYS) / sizeof(uint16_t) == 13, "Months array length");
_Static_assert(MONDAY < SUNDAY, "Weekday ordering");
bool datetime_is_valid(const datetime_t *dt)
{
if (dt == NULL) return false;
// check month first to avoid out-of-bounds read from the MONTH_LENGTHS table
if (!(dt->month >= JANUARY && dt->month <= DECEMBER)) return false;
int monthlen = MONTH_LENGTHS[dt->month];
if (dt->month == FEBRUARY && is_leap_year(dt->year)) {
monthlen = 29;
}
return dt->sec < 60 &&
dt->min < 60 &&
dt->hour < 24 &&
dt->wkday >= MONDAY &&
dt->wkday <= SUNDAY &&
dt->year >= DT_START_YEAR &&
dt->year <= DT_END_YEAR &&
dt->day >= 1 &&
dt->day <= monthlen;
}
bool datetime_set_weekday(datetime_t *dt)
{
dt->wkday = MONDAY; // prevent the validator func erroring out on invalid weekday
if (!datetime_is_valid(dt)) return false;
dt->wkday = date_weekday(dt->year, dt->month, dt->day);
return true;
}
enum weekday date_weekday(uint16_t year, enum month month, uint8_t day)
{
uint16_t days = (DT_START_WKDAY - MONDAY) + (year - DT_START_YEAR) * 365 + MONTH_YEARDAYS[month] + (day - 1);
for (uint16_t i = DT_START_YEAR; i <= year; i++) {
if (is_leap_year(i) && (i < year || month > FEBRUARY)) days++;
}
return MONDAY + days % 7;
}

@ -0,0 +1,72 @@
/*
* util.c
*
* Created on: Aug 12, 2009
* Author: johan
*/
// adapted from libgomspace
#include <string.h>
#include <stdio.h>
#include "common_utils/hexdump.h"
//! Dump memory to debugging output
/**
* Dumps a chunk of memory to the screen
*/
void hex_dump(FILE * fp, void *src, int len) {
int i, j=0, k;
char text[17];
text[16] = '\0';
//printf("Hex dump:\r\n");
fprintf(fp, "%p : ", src);
for(i=0; i<len; i++) {
j++;
fprintf(fp, "%02X ", ((volatile unsigned char *)src)[i]);
if(j == 8)
fputc(' ', fp);
if(j == 16) {
j = 0;
memcpy(text, &((char *)src)[i-15], 16);
for(k=0; k<16; k++) {
if((text[k] < 32) || (text[k] > 126)) {
text[k] = '.';
}
}
fprintf(fp, " |%s|\n\r", text);
if(i<len-1) {
fprintf(fp, "%p : ", src+i+1);
}
}
}
if (i % 16)
fprintf(fp, "\r\n");
}
void hex_dump_buff_line(FILE *fp, int addr_size, unsigned pos, char *line, unsigned len)
{
unsigned i;
fprintf(fp, "%0*x", addr_size, pos);
for (i = 0; i < HEX_DUMP_LINE_BUFF_SIZ; i++)
{
if (!(i % 8))
fputc(' ', fp);
if (i < len)
fprintf(fp, " %02x", (unsigned char)line[i]);
else
fputs(" ", fp);
}
fputs(" |", fp);
for (i = 0; i < HEX_DUMP_LINE_BUFF_SIZ && i < len; i++)
{
if (line[i] >= 32 && line[i] <= 126)
fprintf(fp, "%c", (unsigned char)line[i]);
else
fputc('.', fp);
}
fputs("|\r\n", fp);
}

@ -0,0 +1,9 @@
set(COMPONENT_ADD_INCLUDEDIRS
"include")
set(COMPONENT_SRCDIRS
"src")
set(COMPONENT_REQUIRES ping tcpip_adapter)
register_component()

@ -0,0 +1,27 @@
menu "DHCP watchdog"
config DHCPWD_PERIOD_GW_PING_S
int "Connectivity test interval (s)"
default 60
help
Time between two connectivity tests (gateway ping)
config DHCPWD_GETIP_TIMEOUT_S
int "Timeout to get IP (s)"
default 10
help
Timeout after establishing connection to get an IP address from the DHCP server.
config DHCPWD_TASK_STACK_SIZE
int "Task stack size (bytes)"
default 4096
help
DHCP watchdog task stack size
config DHCPWD_TASK_PRIORITY
int "Task priority"
default 3
help
DHCP watchdog task priority
endmenu

@ -0,0 +1,5 @@
DHCP ping watchdog.
ESP32 sometimes loses wireless connectivity (expiring lease that fails to renew,
AP rebooting and forgetting us, etc). This module periodically pings the gateway
and triggers reconnect if the ping fails.

@ -0,0 +1,3 @@
COMPONENT_SRCDIRS := src
COMPONENT_ADD_INCLUDEDIRS := include

@ -0,0 +1,82 @@
/**
* DHCP watchdog
*
* This is a workaround for a rare case where we don't get
* any IP after connecting with STA. If it takes too long,
* try power-cycling the DHCP client. If that fails too,
* try cycling the WiFi stack too.
*
* This does not try to reboot, as there are valid cases when this
* can happen - e.g. no DHCP on the network + no static IP configured yet.
*
* The ping component is used as a dependency.
*/
#ifndef _DHCP_WD_H_
#define _DHCP_WD_H_
#include "esp_netif.h"
#include "esp_event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
typedef struct dhcp_wd_instance * dhcp_wd_handle_t;
/**
* Start the watchdog. Handle must remain valid until the task is deleted.
*
* @param[in] iface
* @param[out] handle - pointer to a handle variable (will be written to it)
* @return success
*/
esp_err_t dhcp_watchdog_start(esp_netif_t * netif, bool is_wifi, dhcp_wd_handle_t *pHandle);
/**
* Check if a watchdog is running
*
* @param[in] handle
* @return is running
*/
bool dhcp_watchdog_is_running(dhcp_wd_handle_t handle);
/**
* Stop the watchdog and free resources.
* The handle becomes invalid and is set to NULL.
*
* @param[in] handle
* @return success
*/
esp_err_t dhcp_watchdog_stop(dhcp_wd_handle_t *pHandle);
enum dhcp_wd_event {
DHCP_WD_NOTIFY_CONNECTED,
DHCP_WD_NOTIFY_DISCONNECTED,
DHCP_WD_NOTIFY_GOT_IP,
};
/**
* @brief Notify the watchdog task about a wifi state change
*
* Call this from the WiFi event handler.
*
* @param[in] handle
* @param[in] event - detected event
*/
esp_err_t dhcp_watchdog_notify(dhcp_wd_handle_t handle, enum dhcp_wd_event);
enum dhcp_wd_test_result {
DHCP_WD_RESULT_OK = 0,
DHCP_WD_RESULT_PING_LOST,
DHCP_WD_RESULT_NO_GATEWAY,
};
/**
* Manually trigger a connection test by pinging the gateway.
* This is independent on any watchdog tasks and can be run without starting the watchdog.
*
* @param[in] iface - network interface, typically TCPIP_ADAPTER_IF_STA
* @return test result
*/
enum dhcp_wd_test_result dhcp_wd_test_connection(esp_netif_t *iface);
#endif //_DHCP_WD_H_

@ -0,0 +1,277 @@
#include <string.h>
#include "esp_log.h"
#include "dhcp_wd.h"
#include "esp_wifi.h"
//#include "esp_eth.h"
#include "ping.h"
#define xstr(s) str(s)
#define str(s) #s
#define PERIOD_GW_PING_S CONFIG_DHCPWD_PERIOD_GW_PING_S
#define GETIP_TIMEOUT_S CONFIG_DHCPWD_GETIP_TIMEOUT_S
#define TASK_STACK_SIZE CONFIG_DHCPWD_TASK_STACK_SIZE
#define TASK_PRIO CONFIG_DHCPWD_TASK_PRIORITY
static const char *TAG = "dhcp_wd";
static void dhcp_watchdog_task(void *parm);
struct dhcp_wd_instance {
TaskHandle_t task;
esp_netif_t * iface;
bool is_wifi;
bool running;
};
#define STATES_ENUM \
X(DISCONECTED) \
X(CONECTED_WAIT_IP) \
X(CONECTED_WAIT_IP2) \
X(CONECTED)
enum dhcp_wd_state {
#undef X
#define X(s) STATE_##s,
STATES_ENUM
};
const char *state_names[] = {
#undef X
#define X(s) xstr(s),
STATES_ENUM
};
enum dhcp_wd_notify {
NOTIFY_CONNECTED = BIT0,
NOTIFY_DISCONNECTED = BIT1,
NOTIFY_GOT_IP = BIT2,
NOTIFY_SHUTDOWN = BIT3, // kills the task
};
/** Send a notification to the watchdog task */
esp_err_t dhcp_watchdog_notify(dhcp_wd_handle_t handle, const enum dhcp_wd_event event)
{
assert(handle != NULL);
assert(handle->task != NULL);
uint32_t flag = 0;
switch (event) {
case DHCP_WD_NOTIFY_CONNECTED:
flag = NOTIFY_CONNECTED;
break;
case DHCP_WD_NOTIFY_DISCONNECTED:
flag = NOTIFY_DISCONNECTED;
break;
case DHCP_WD_NOTIFY_GOT_IP:
flag = NOTIFY_GOT_IP;
break;
default:
break;
}
BaseType_t ret = pdPASS;
if (flag != 0) {
ret = xTaskNotify(handle->task, flag, eSetBits);
}
return (pdPASS == ret) ? ESP_OK : ESP_FAIL;
}
/**
* Start the watchdog
*/
esp_err_t dhcp_watchdog_start(esp_netif_t * netif, bool is_wifi, dhcp_wd_handle_t *pHandle)
{
assert(pHandle != NULL);
dhcp_wd_handle_t handle = calloc(1, sizeof(struct dhcp_wd_instance));
if (!handle) return ESP_ERR_NO_MEM;
*pHandle = handle;
handle->iface = netif;
handle->is_wifi = is_wifi;
BaseType_t ret = xTaskCreate(dhcp_watchdog_task, "dhcp-wd", TASK_STACK_SIZE, (void *)handle, TASK_PRIO, &handle->task);
handle->running = true;
return (pdPASS == ret) ? ESP_OK : ESP_FAIL;
}
/**
* Check if a watchdog is still running
*
* @param handle
* @return is running
*/
bool dhcp_watchdog_is_running(dhcp_wd_handle_t handle)
{
return handle->running;
}
/**
* Stop the watchdog and free resources
*/
esp_err_t dhcp_watchdog_stop(dhcp_wd_handle_t *pHandle)
{
assert(pHandle != NULL);
assert(*pHandle != NULL);
xTaskNotify((*pHandle)->task, NOTIFY_SHUTDOWN, eSetBits);
*pHandle = NULL;
return ESP_OK;
}
/**
* @param parm - tcpip_adapter_if_t iface (cast to void *) - typically TCPIP_ADAPTER_IF_STA
*/
static void dhcp_watchdog_task(void *parm)
{
enum dhcp_wd_state state = STATE_DISCONECTED;
dhcp_wd_handle_t handle = parm;
assert(handle != NULL);
assert(handle->iface != NULL);
ESP_LOGI(TAG, "Watchdog started");
while (1) {
uint32_t flags = 0;
uint32_t wait_s;
TickType_t waittime;
switch (state) {
case STATE_DISCONECTED:
wait_s = waittime = portMAX_DELAY;
break;
case STATE_CONECTED_WAIT_IP:
case STATE_CONECTED_WAIT_IP2:
wait_s = GETIP_TIMEOUT_S;
waittime = (GETIP_TIMEOUT_S * 1000) / portTICK_PERIOD_MS;
break;
case STATE_CONECTED:
wait_s = PERIOD_GW_PING_S;
waittime = (PERIOD_GW_PING_S * 1000) / portTICK_PERIOD_MS;
break;
default:
assert(0);
}
ESP_LOGD(TAG, "State %s, wait %d s", state_names[state], wait_s);
BaseType_t rv = xTaskNotifyWait(
/* no clear on entry */ pdFALSE,
/* clear all on exit */ ULONG_MAX,
&flags, waittime);
if (rv == pdPASS) {
// the order here is important in case we get multiple events at once
if (flags & NOTIFY_DISCONNECTED) {
state = STATE_DISCONECTED;
}
if (flags & NOTIFY_CONNECTED) {
state = STATE_CONECTED_WAIT_IP;
}
if (flags & NOTIFY_GOT_IP) {
state = STATE_CONECTED;
}
if (flags & NOTIFY_SHUTDOWN) {
// kill self
handle->running = false;
free(handle);
vTaskDelete(NULL);
return;
}
} else {
// a timeout occurred
switch (state) {
case STATE_DISCONECTED:
// this shouldn't happen, we have infinite delay waiting for disconnected
ESP_LOGW(TAG, "dhcp_wd double discon evt");
break;
case STATE_CONECTED_WAIT_IP:
ESP_LOGW(TAG, "Get IP timeout, restarting DHCP client");
// this is a bit suspicious
// try to restart the DHCPC client
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(handle->iface));
ESP_ERROR_CHECK(esp_netif_dhcpc_start(handle->iface));
state = STATE_CONECTED_WAIT_IP2;
break;
case STATE_CONECTED_WAIT_IP2:
ESP_LOGW(TAG, "Get IP timeout 2, restarting network stack");
// well now this is weird. try flipping the whole WiFi/Eth stack
if (handle->is_wifi) {
ESP_ERROR_CHECK(esp_wifi_disconnect());
}
// this will trigger the disconnected event and loop back into Disconnected
// the disconnect event handler calls connect again
state = STATE_DISCONECTED;
break;
case STATE_CONECTED: {
// Ping gateway to check if we're still connected
enum dhcp_wd_test_result result = dhcp_wd_test_connection(handle->iface);
if (result == DHCP_WD_RESULT_PING_LOST) {
// looks like the gateway silently dropped us
// try kicking the DHCP client, if it helps
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(handle->iface));
ESP_ERROR_CHECK(esp_netif_dhcpc_start(handle->iface));
state = STATE_CONECTED_WAIT_IP2;
// if not, it'll flip the whole wifi stack
} else {
ESP_LOGD(TAG, "Gateway ping OK");
}
break;
}
}
}
}
}
enum dhcp_wd_test_result dhcp_wd_test_connection(esp_netif_t *iface)
{
ESP_LOGD(TAG, "Ping Gateway to check if IP is valid");
ping_opts_t opts = PING_CONFIG_DEFAULT();
opts.count = 3;
opts.interval_ms = 0;
opts.timeout_ms = 1000;
esp_netif_ip_info_t ip_info = {};
ESP_ERROR_CHECK(esp_netif_get_ip_info(iface, &ip_info));
opts.ip_addr.addr = ip_info.gw.addr;
ping_result_t result = {};
if (ip_info.gw.addr != 0) {
esp_err_t ret = ping(&opts, &result);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Ping error");
return DHCP_WD_RESULT_PING_LOST;
}
ESP_LOGD(TAG, "Ping result: %d tx, %d rx", result.sent, result.received);
if (result.received == 0) {
ESP_LOGW(TAG, "Failed to ping GW");
return DHCP_WD_RESULT_PING_LOST;
} else {
return DHCP_WD_RESULT_OK;
}
} else {
ESP_LOGW(TAG, "No GW IP to ping");
return DHCP_WD_RESULT_NO_GATEWAY;
}
}

@ -0,0 +1,16 @@
set(COMPONENT_ADD_INCLUDEDIRS
"include" "files")
set(COMPONENT_SRCDIRS
"src" "files")
set(COMPONENT_REQUIRES tcpip_adapter esp_http_server httpd_utils common_utils)
#begin staticfiles
# generated by rebuild_file_tables
set(COMPONENT_EMBED_FILES
"files/embed/favicon.ico"
"files/embed/index.html")
#end staticfiles
register_component()

@ -0,0 +1,2 @@
File and template serving support for the http_server bundled with ESP-IDF.

@ -0,0 +1,3 @@
COMPONENT_SRCDIRS := src
COMPONENT_ADD_INCLUDEDIRS := include

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,106 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ESP node</title>
<style>
html { font-family:sans-serif }
table { border-collapse:collapse; }
td,th { padding: 4px 10px; min-width: 65px; }
td { min-width: 70px; }
th { text-align:left; }
tbody th { text-align:center; }
tbody th {border-right: 1px solid black;}
thead th { text-align:center;padding-top:0; }
td { text-align:right; }
td[c],[c] td { text-align:center; }
td[l],[l] td { text-align:left; }
td[r],[r] td { text-align:right; }
td[ed] {cursor:pointer;}
td[ed]:hover {text-decoration:underline;}
table[thl] tbody th {text-align:left;}
figure {
border-top:3px solid black;
border-bottom:3px solid black;
display: inline-block;
padding: 5px 0;
margin: 0 0 15px 15px;
}
thead tr { border-bottom: 2px solid black; }
</style>
</head>
<body>
<h1>ESP node {version}</h1>
<a href="/reboot">Restart node</a>
<script>
var kv_re = /^(-?[0-9.-]+)\s+(.+)$/;
var Qi = function (x) { return document.getElementById(x) };
var data = {};
function update(data) {
if (data) {
var rows = data.split('\x1e');
rows.forEach(function (v) {
var kv = v.split('\x1f');
var el = Qi(kv[0]);
if (!el) return;
var suf = '';
var res = kv_re.exec(el.textContent);
if (res) suf = ' ' + res[2];
el.textContent = kv[1] + suf;
data[kv[0]] = kv[1];
});
} else {
var xhr=new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState===4){
if (xhr.status===200) {
update(xhr.responseText);
}
setTimeout(update, 1000);
}
};
xhr.onerror = function () {
setTimeout(update, 1000);
};
xhr.open('GET', '/data');
xhr.send();
}
}
function mkXhr() {
var xhr=new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState===4 && xhr.status!==200) {
alert(xhr.responseText || xhr.statusText || 'Request failed');
}
};
return xhr;
}
function editOpt() {
var k = this.id;
var v = prompt(k+' =', data[k]||'');
if (v !== null) {
var xhr = mkXhr();
xhr.open('POST', '/set');
xhr.send("key="+k+'&value='+encodeURIComponent(v));
}
}
function toggleOpt() {
var xhr = mkXhr();
xhr.open('POST', '/toggle');
xhr.send("x="+this.id);
}
/*
setTimeout(update, 500);
for(var i=1;i<8;i++) {
if(i<7) Qi('i'+i+'_max').addEventListener('click', editOpt);
Qi('ch'+i+'_state').addEventListener('click', toggleOpt);
}
*/
</script>

@ -0,0 +1,163 @@
#!/usr/bin/env php
<?php
// This script rebuilds the static files enum, extern symbols pointing to the embedded byte buffers,
// and the look-up structs table. To add more files, simply add them in the 'files' directory.
// Note that all files will be accessible by the webserver, unless you filter them in embedded_files.c.
// List all files
$files = scandir(__DIR__.'/embed');
$files = array_filter(array_map(function ($f) {
if (!is_file(__DIR__.'/embed/'.$f)) return null;
if (preg_match('/^\.|\.kate-swp|\.bak$|~$|\.sh$/', $f)) return null;
echo "Found: $f\n";
return $f;
}, $files));
sort($files);
$formatted = array_filter(array_map(function ($f) {
return "\"files/embed/$f\"";
}, $files));
$cmake = file_get_contents(__DIR__.'/../CMakeLists.txt');
$cmake = preg_replace('/#begin staticfiles\n.*#end staticfiles/s',
"#begin staticfiles\n".
"# generated by rebuild_file_tables\n".
"set(COMPONENT_EMBED_FILES\n ".
implode("\n ", $formatted) . ")\n".
"#end staticfiles",
$cmake);
file_put_contents(__DIR__.'/../CMakeLists.txt', $cmake);
// Generate a list of files
$num = 0;
$enum_keys = array_map(function ($f) use(&$num) {
$a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f));
return 'FILE_'. $a.' = '.($num++);
}, $files);
$keylist = implode(",\n ", $enum_keys);
$struct_array = [];
$externs = array_map(function ($f) use (&$struct_array) {
$a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f));
$start = '_binary_'. strtolower($a).'_start';
$end = '_binary_'. strtolower($a).'_end';
static $mimes = array(
'txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'php' => 'text/html',
'css' => 'text/css',
'js' => 'application/javascript',
'json' => 'application/json',
'xml' => 'application/xml',
'swf' => 'application/x-shockwave-flash',
'flv' => 'video/x-flv',
'pem' => 'application/x-pem-file',
// images
'png' => 'image/png',
'jpe' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
'ico' => 'image/vnd.microsoft.icon',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'svg' => 'image/svg+xml',
'svgz' => 'image/svg+xml',
// archives
'zip' => 'application/zip',
'rar' => 'application/x-rar-compressed',
'exe' => 'application/x-msdownload',
'msi' => 'application/x-msdownload',
'cab' => 'application/vnd.ms-cab-compressed',
// audio/video
'mp3' => 'audio/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
// adobe
'pdf' => 'application/pdf',
'psd' => 'image/vnd.adobe.photoshop',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
// ms office
'doc' => 'application/msword',
'rtf' => 'application/rtf',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
// open office
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
);
$parts = explode('.', $f);
$suffix = end($parts);
$mime = $mimes[$suffix] ?? 'application/octet-stream';
$len = filesize('embed/'.$f);
$struct_array[] = "[FILE_$a] = {{$start}, {$end}, \"{$f}\", \"{$mime}\"},";
return
'extern const uint8_t '.$start.'[];'."\n".
'extern const uint8_t '.$end.'[];';
}, $files);
$externlist = implode("\n", $externs);
$structlist = implode("\n ", $struct_array);
file_put_contents('www_files_enum.h', <<<FILE
// Generated by 'rebuild_file_tables'
#ifndef _EMBEDDED_FILES_ENUM_H
#define _EMBEDDED_FILES_ENUM_H
#include "fileserver/embedded_files.h"
enum embedded_file_id {
$keylist
};
#endif // _EMBEDDED_FILES_ENUM_H
FILE
);
$files_count = count($struct_array);
file_put_contents("www_files_enum.c", <<<FILE
// Generated by 'rebuild_file_tables'
#include <stdint.h>
#include "www_files_enum.h"
$externlist
const struct embedded_file_info EMBEDDED_FILE_LOOKUP[] = {
$structlist
};
const size_t EMBEDDED_FILE_LOOKUP_LEN = $files_count;
FILE
);

@ -0,0 +1,15 @@
// Generated by 'rebuild_file_tables'
#include <stdint.h>
#include "www_files_enum.h"
extern const uint8_t _binary_favicon_ico_start[];
extern const uint8_t _binary_favicon_ico_end[];
extern