master
Ondřej Hruška 2 years ago
commit ddaa193821
  1. 3
      .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. 39
      components/esp32-ds18b20/.travis.yml
  22. 6
      components/esp32-ds18b20/CMakeLists.txt
  23. 21
      components/esp32-ds18b20/LICENSE
  24. 98
      components/esp32-ds18b20/README.md
  25. 1
      components/esp32-ds18b20/component.mk
  26. 3
      components/esp32-ds18b20/doc/.gitignore
  27. 2496
      components/esp32-ds18b20/doc/Doxyfile
  28. 614
      components/esp32-ds18b20/ds18b20.c
  29. 206
      components/esp32-ds18b20/include/ds18b20.h
  30. 26
      components/esp32-ds18b20/library.json
  31. 39
      components/esp32-owb/.travis.yml
  32. 5
      components/esp32-owb/CMakeLists.txt
  33. 21
      components/esp32-owb/LICENSE
  34. 60
      components/esp32-owb/README.md
  35. 1
      components/esp32-owb/component.mk
  36. 3
      components/esp32-owb/doc/.gitignore
  37. 2496
      components/esp32-owb/doc/Doxyfile
  38. 336
      components/esp32-owb/include/owb.h
  39. 70
      components/esp32-owb/include/owb_gpio.h
  40. 75
      components/esp32-owb/include/owb_rmt.h
  41. 30
      components/esp32-owb/library.json
  42. 744
      components/esp32-owb/owb.c
  43. 287
      components/esp32-owb/owb_gpio.c
  44. 469
      components/esp32-owb/owb_rmt.c
  45. 7
      components/modbus_slave/CMakeLists.txt
  46. 1
      components/modbus_slave/README.txt
  47. 3
      components/modbus_slave/component.mk
  48. 103
      components/modbus_slave/include/modbus.h
  49. 129
      components/modbus_slave/include/pp/payload_builder.h
  50. 167
      components/modbus_slave/include/pp/payload_parser.h
  51. 32
      components/modbus_slave/include/pp/type_coerce.h
  52. 475
      components/modbus_slave/src/modbus.c
  53. 87
      components/modbus_slave/src/pp/payload_builder.c
  54. 104
      components/modbus_slave/src/pp/payload_parser.c
  55. 4
      components/ping/CMakeLists.txt
  56. 1
      components/ping/README.txt
  57. 3
      components/ping/component.mk
  58. 58
      components/ping/include/ping.h
  59. 262
      components/ping/src/ping.c
  60. 4
      components/socket_server/CMakeLists.txt
  61. 1
      components/socket_server/README.txt
  62. 3
      components/socket_server/component.mk
  63. 282
      components/socket_server/include/socket_server.h
  64. 1015
      components/socket_server/src/socket_server.c
  65. 18
      components/vconsole/CMakeLists.txt
  66. 54
      components/vconsole/libconsole/.gitignore
  67. 73
      components/vconsole/libconsole/CMakeLists.txt
  68. 1
      components/vconsole/libconsole/LICENSE.txt
  69. 24
      components/vconsole/libconsole/include/console/cmddef.h
  70. 22
      components/vconsole/libconsole/include/console/config.h.in
  71. 362
      components/vconsole/libconsole/include/console/console.h
  72. 196
      components/vconsole/libconsole/include/console/console_io.h
  73. 94
      components/vconsole/libconsole/include/console/prefix_match.h
  74. 213
      components/vconsole/libconsole/include/console/utils.h
  75. 9
      components/vconsole/libconsole/lib/argtable3/CMakeLists.txt
  76. 3
      components/vconsole/libconsole/lib/argtable3/README.txt
  77. 4969
      components/vconsole/libconsole/lib/argtable3/argtable3.c
  78. 306
      components/vconsole/libconsole/lib/argtable3/argtable3.h
  79. 1824
      components/vconsole/libconsole/src/console.c
  80. 42
      components/vconsole/libconsole/src/console_filecap.c
  81. 194
      components/vconsole/libconsole/src/console_io.c
  82. 1129
      components/vconsole/libconsole/src/console_linenoise.c
  83. 248
      components/vconsole/libconsole/src/console_linenoise.h
  84. 196
      components/vconsole/libconsole/src/console_prefix_match.c
  85. 120
      components/vconsole/libconsole/src/console_split_argv.c
  86. 36
      components/vconsole/libconsole/src/console_split_argv.h
  87. 204
      components/vconsole/libconsole/src/console_utils.c
  88. 645
      components/vconsole/libconsole/src/queue.h
  89. 48
      main/CMakeLists.txt
  90. 27
      main/Kconfig.projbuild
  91. 194
      main/actuators.c
  92. 29
      main/actuators.h
  93. 26
      main/application.h
  94. 4
      main/component.mk
  95. 48
      main/console/cmd_common.h
  96. 34
      main/console/commands/cmd_dump.c
  97. 50
      main/console/commands/cmd_factory_reset.c
  98. 38
      main/console/commands/cmd_heap.c
  99. 341
      main/console/commands/cmd_ip.c
  100. 52
      main/console/commands/cmd_pw.c
  101. Some files were not shown because too many files have changed in this diff Show More

3
.gitignore vendored

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

@ -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(geiger)

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := fanctl
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,39 @@
# Build and deploy doxygen documention to GitHub Pages
sudo: false
dist: trusty
# Blacklist
branches:
only:
- master
# Environment variables
env:
global:
- GH_REPO_REF: github.com/DavidAntliff/esp32-ds18b20.git
# Install dependencies
addons:
apt:
packages:
- doxygen
- doxygen-doc
- doxygen-latex
- doxygen-gui
- graphviz
# Build the docs
script:
- cd doc
- doxygen
# Deploy using Travis-CI/GitHub Pages integration support
deploy:
provider: pages
skip-cleanup: true
local-dir: doc/html
github-token: $GITHUB_TOKEN
on:
branch: master
target-branch: gh-pages

@ -0,0 +1,6 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_SRCS "ds18b20.c")
set(COMPONENT_PRIV_REQUIRES "esp32-owb")
register_component()

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 David Antliff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,98 @@
# esp32-ds18b20
## Introduction
This is a ESP32-compatible C component for the Maxim Integrated DS18B20 Programmable Resolution 1-Wire Digital
Thermometer device.
It supports multiple devices on the same 1-Wire bus.
It is written and tested for v2.1, v3.0-3.3 and v4.1-beta1 of the [ESP-IDF](https://github.com/espressif/esp-idf)
environment, using the xtensa-esp32-elf toolchain (gcc version 5.2.0).
## Dependencies
Requires [esp32-owb](https://github.com/DavidAntliff/esp32-owb).
## Example
See [esp32-ds18b20-example](https://github.com/DavidAntliff/esp32-ds18b20-example) for an example that supports single
and multiple devices on a single bus.
## Features
In cooperation with the underlying esp32-owb component, this component includes:
* External power supply mode.
* Parasitic power mode (VDD and GND connected) - see notes below.
* Static (stack-based) or dynamic (malloc-based) memory model.
* No globals - support any number of DS18B20 devices on any number of 1-Wire buses simultaneously.
* 1-Wire device detection and validation, including search for multiple devices on a single bus.
* Addressing optimisation for a single (solo) device on a bus.
* CRC checks on temperature data.
* Programmable temperature measurement resolution (9, 10, 11 or 12-bit resolution).
* Temperature conversion and retrieval.
* Separation of conversion and temperature retrieval to allow for simultaneous conversion across multiple devices.
## Parasitic Power Mode
Consult the [datasheet](http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf) for more detailed information about
Parasitic Power mode.
Parasitic power operation can be detected by `ds18b20_check_for_parasite_power()` followed by a call to
`owb_use_parasitic_power()`, or simply set explicitly by a call to the latter.
This library has been tested on the ESP32 with two parasitic-power configurations, with two DS18B20 devices at 3.3V:
1. Disconnect power to each device's VDD pin, and connect that pin to GND for each device. Power is supplied to
each device through the OWB data line.
2. Connect the OWB data line to VCC via a P-channel MOSFET (e.g. BS250) and current-limiting resistor (e.g. 270 Ohm).
Ensure your code calls `owb_use_strong_pullup_gpio()` with the GPIO connected to the MOSFET Gate. The GPIO will go
high during temperature conversion, turning on the MOSFET and providing power from VCC to each device through the OWB
data line.
If you set up configuration 1 and do not see CRC errors, but you get incorrect temperature readings around 80 - 85
degrees C, then it is likely that your DS18B20 devices are running out of power during the temperature conversion. In
this case, consider reducing the value of the pull-up resistor. For example, I have had success obtaining correct
conversions from two parasitic DS18B20 devices by replacing the 4k7 Ohm pull-up resistor with a 1 kOhm resistor.
Alternatively, consider using configuration 2.
Note that use of parasitic power mode disables the ability for devices on the bus to signal that an operation has
completed. This means that DS18B20 devices in parasitic power mode are not able to communicate when they have completed
a temperature conversion. In this mode, a delay for a pre-calculated duration occurs, and then the conversion result is
read from the device(s). *If your ESP32 is not running on the correct clock rate, this duration may be too short!*
## Documentation
Automatically generated API documentation (doxygen) is available [here](https://davidantliff.github.io/esp32-ds18b20/index.html).
## Source Code
The source is available from [GitHub](https://www.github.com/DavidAntliff/esp32-ds18b20).
## License
The code in this project is licensed under the MIT license - see LICENSE for details.
## Links
* [DS18B20 Datasheet](http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf)
* [1-Wire Communication Through Software](https://www.maximintegrated.com/en/app-notes/index.mvp/id/126)
* [1-Wire Search Algorithm](https://www.maximintegrated.com/en/app-notes/index.mvp/id/187)
* [Espressif IoT Development Framework for ESP32](https://github.com/espressif/esp-idf)
## Acknowledgements
Parts of this code are based on references provided to the public domain by Maxim Integrated.
"1-Wire" is a registered trademark of Maxim Integrated.
## Roadmap
The following features are anticipated but not yet implemented:
* Concurrency support (multiple tasks accessing devices on the same bus).
* Alarm support.
* EEPROM support.
* Parasitic power support.

@ -0,0 +1,3 @@
html/
latex/

File diff suppressed because it is too large Load Diff

@ -0,0 +1,614 @@
/*
* MIT License
*
* Copyright (c) 2017-2019 David Antliff
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file ds18b20.c
*
* Resolution is cached in the DS18B20_Info object to avoid querying the hardware
* every time a temperature conversion is required. However this can result in the
* cached value becoming inconsistent with the hardware value, so care must be taken.
*
*/
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_log.h"
#include "ds18b20.h"
#include "owb.h"
static const char * TAG = "ds18b20";
static const int T_CONV = 750; // maximum conversion time at 12-bit resolution in milliseconds
// Function commands
#define DS18B20_FUNCTION_TEMP_CONVERT 0x44 ///< Initiate a single temperature conversion
#define DS18B20_FUNCTION_SCRATCHPAD_WRITE 0x4E ///< Write 3 bytes of data to the device scratchpad at positions 2, 3 and 4
#define DS18B20_FUNCTION_SCRATCHPAD_READ 0xBE ///< Read 9 bytes of data (including CRC) from the device scratchpad
#define DS18B20_FUNCTION_SCRATCHPAD_COPY 0x48 ///< Copy the contents of the scratchpad to the device EEPROM
#define DS18B20_FUNCTION_EEPROM_RECALL 0xB8 ///< Restore alarm trigger values and configuration data from EEPROM to the scratchpad
#define DS18B20_FUNCTION_POWER_SUPPLY_READ 0xB4 ///< Determine if a device is using parasitic power
/// @cond ignore
typedef struct
{
uint8_t temperature[2]; // [0] is LSB, [1] is MSB
uint8_t trigger_high;
uint8_t trigger_low;
uint8_t configuration;
uint8_t reserved[3];
uint8_t crc;
} __attribute__((packed)) Scratchpad;
/// @endcond ignore
static void _init(DS18B20_Info * ds18b20_info, const OneWireBus * bus)
{
if (ds18b20_info != NULL)
{
ds18b20_info->bus = bus;
memset(&ds18b20_info->rom_code, 0, sizeof(ds18b20_info->rom_code));
ds18b20_info->use_crc = false;
ds18b20_info->resolution = DS18B20_RESOLUTION_INVALID;
ds18b20_info->solo = false; // assume multiple devices unless told otherwise
ds18b20_info->init = true;
}
else
{
ESP_LOGE(TAG, "ds18b20_info is NULL");
}
}
static bool _is_init(const DS18B20_Info * ds18b20_info)
{
bool ok = false;
if (ds18b20_info != NULL)
{
if (ds18b20_info->init)
{
// OK
ok = true;
}
else
{
ESP_LOGE(TAG, "ds18b20_info is not initialised");
}
}
else
{
ESP_LOGE(TAG, "ds18b20_info is NULL");
}
return ok;
}
static bool _address_device(const DS18B20_Info * ds18b20_info)
{
bool present = false;
if (_is_init(ds18b20_info))
{
owb_reset(ds18b20_info->bus, &present);
if (present)
{
if (ds18b20_info->solo)
{
// if there's only one device on the bus, we can skip
// sending the ROM code and instruct it directly
owb_write_byte(ds18b20_info->bus, OWB_ROM_SKIP);
}
else
{
// if there are multiple devices on the bus, a Match ROM command
// must be issued to address a specific slave
owb_write_byte(ds18b20_info->bus, OWB_ROM_MATCH);
owb_write_rom_code(ds18b20_info->bus, ds18b20_info->rom_code);
}
}
else
{
ESP_LOGE(TAG, "ds18b20 device not responding");
}
}
return present;
}
static bool _check_resolution(DS18B20_RESOLUTION resolution)
{
return (resolution >= DS18B20_RESOLUTION_9_BIT) && (resolution <= DS18B20_RESOLUTION_12_BIT);
}
static float _wait_for_duration(DS18B20_RESOLUTION resolution)
{
int64_t start_time = esp_timer_get_time();
if (_check_resolution(resolution))
{
int divisor = 1 << (DS18B20_RESOLUTION_12_BIT - resolution);
ESP_LOGD(TAG, "divisor %d", divisor);
float max_conversion_time = (float)T_CONV / (float)divisor;
int ticks = ceil(max_conversion_time / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "wait for conversion: %.3f ms, %d ticks", max_conversion_time, ticks);
// wait at least this maximum conversion time
vTaskDelay(ticks);
}
int64_t end_time = esp_timer_get_time();
return (float)(end_time - start_time) / 1000000.0f;
}
static float _wait_for_device_signal(const DS18B20_Info * ds18b20_info)
{
float elapsed_time = 0.0f;
if (_check_resolution(ds18b20_info->resolution))
{
int divisor = 1 << (DS18B20_RESOLUTION_12_BIT - ds18b20_info->resolution);
// allow for 10% overtime
float max_conversion_time = (float)T_CONV / (float)divisor * 1.1;
int max_conversion_ticks = ceil(max_conversion_time / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "wait for conversion: max %.0f ms, %d ticks", max_conversion_time, max_conversion_ticks);
// wait for conversion to complete - all devices will pull bus low once complete
TickType_t start_ticks = xTaskGetTickCount();
TickType_t duration_ticks = 0;
uint8_t status = 0;
do
{
vTaskDelay(1);
owb_read_bit(ds18b20_info->bus, &status);
duration_ticks = xTaskGetTickCount() - start_ticks;
} while (status == 0 && duration_ticks < max_conversion_ticks);
elapsed_time = duration_ticks * portTICK_PERIOD_MS;
if (duration_ticks >= max_conversion_ticks)
{
ESP_LOGW(TAG, "conversion timed out");
}
else
{
ESP_LOGD(TAG, "conversion took at most %.0f ms", elapsed_time);
}
}
return elapsed_time;
}
static float _decode_temp(uint8_t lsb, uint8_t msb, DS18B20_RESOLUTION resolution)
{
float result = 0.0f;
if (_check_resolution(resolution))
{
// masks to remove undefined bits from result
static const uint8_t lsb_mask[4] = { ~0x07, ~0x03, ~0x01, ~0x00 };
uint8_t lsb_masked = lsb_mask[resolution - DS18B20_RESOLUTION_9_BIT] & lsb;
int16_t raw = (msb << 8) | lsb_masked;
result = raw / 16.0f;
}
else
{
ESP_LOGE(TAG, "Unsupported resolution %d", resolution);
}
return result;
}
static size_t _min(size_t x, size_t y)
{
return x > y ? y : x;
}
static DS18B20_ERROR _read_scratchpad(const DS18B20_Info * ds18b20_info, Scratchpad * scratchpad, size_t count)
{
// If CRC is enabled, regardless of count, read the entire scratchpad and verify the CRC,
// otherwise read up to the scratchpad size, or count, whichever is smaller.
if (!scratchpad) {
return DS18B20_ERROR_NULL;
}
DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN;
if (ds18b20_info->use_crc)
{
count = sizeof(Scratchpad);
}
count = _min(sizeof(Scratchpad), count); // avoid reading past end of scratchpad
ESP_LOGD(TAG, "scratchpad read: CRC %d, count %d", ds18b20_info->use_crc, count);
if (_address_device(ds18b20_info))
{
// read scratchpad
if (owb_write_byte(ds18b20_info->bus, DS18B20_FUNCTION_SCRATCHPAD_READ) == OWB_STATUS_OK)
{
if (owb_read_bytes(ds18b20_info->bus, (uint8_t *)scratchpad, count) == OWB_STATUS_OK)
{
ESP_LOG_BUFFER_HEX_LEVEL(TAG, scratchpad, count, ESP_LOG_DEBUG);
err = DS18B20_OK;
if (!ds18b20_info->use_crc)
{
// Without CRC, or partial read:
ESP_LOGD(TAG, "No CRC check");
bool is_present = false;
owb_reset(ds18b20_info->bus, &is_present); // terminate early
}
else
{
// With CRC:
if (owb_crc8_bytes(0, (uint8_t *)scratchpad, sizeof(*scratchpad)) != 0)
{
ESP_LOGE(TAG, "CRC failed");
err = DS18B20_ERROR_CRC;
}
else
{
ESP_LOGD(TAG, "CRC ok");
}
}
}
else
{
ESP_LOGE(TAG, "owb_read_bytes failed");
err = DS18B20_ERROR_OWB;
}
}
else
{
ESP_LOGE(TAG, "owb_write_byte failed");
err = DS18B20_ERROR_OWB;
}
}
else
{
err = DS18B20_ERROR_DEVICE;
}
return err;
}
static bool _write_scratchpad(const DS18B20_Info * ds18b20_info, const Scratchpad * scratchpad, bool verify)
{
bool result = false;
// Only bytes 2, 3 and 4 (trigger and configuration) can be written.
// All three bytes MUST be written before the next reset to avoid corruption.
if (_is_init(ds18b20_info))
{
if (_address_device(ds18b20_info))
{
ESP_LOGD(TAG, "scratchpad write 3 bytes:");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, &scratchpad->trigger_high, 3, ESP_LOG_DEBUG);
owb_write_byte(ds18b20_info->bus, DS18B20_FUNCTION_SCRATCHPAD_WRITE);
owb_write_bytes(ds18b20_info->bus, (uint8_t *)&scratchpad->trigger_high, 3);
result = true;
if (verify)
{
Scratchpad read = {0};
if (_read_scratchpad(ds18b20_info, &read, offsetof(Scratchpad, configuration) + 1) == DS18B20_OK)
{
if (memcmp(&scratchpad->trigger_high, &read.trigger_high, 3) != 0)
{
ESP_LOGE(TAG, "scratchpad verify failed: "
"wrote {0x%02x, 0x%02x, 0x%02x}, "
"read {0x%02x, 0x%02x, 0x%02x}",
scratchpad->trigger_high, scratchpad->trigger_low, scratchpad->configuration,
read.trigger_high, read.trigger_low, read.configuration);
result = false;
}
}
else
{
ESP_LOGE(TAG, "read scratchpad failed");
result = false;
}
}
}
}
return result;
}
// Public API
DS18B20_Info * ds18b20_malloc(void)
{
DS18B20_Info * ds18b20_info = malloc(sizeof(*ds18b20_info));
if (ds18b20_info != NULL)
{
memset(ds18b20_info, 0, sizeof(*ds18b20_info));
ESP_LOGD(TAG, "malloc %p", ds18b20_info);
}
else
{
ESP_LOGE(TAG, "malloc failed");
}
return ds18b20_info;
}
void ds18b20_free(DS18B20_Info ** ds18b20_info)
{
if (ds18b20_info != NULL && (*ds18b20_info != NULL))
{
ESP_LOGD(TAG, "free %p", *ds18b20_info);
free(*ds18b20_info);
*ds18b20_info = NULL;
}
}
void ds18b20_init(DS18B20_Info * ds18b20_info, const OneWireBus * bus, OneWireBus_ROMCode rom_code)
{
if (ds18b20_info != NULL)
{
_init(ds18b20_info, bus);
ds18b20_info->rom_code = rom_code;
// read current resolution from device as it may not be power-on or factory default
ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info);
}
else
{
ESP_LOGE(TAG, "ds18b20_info is NULL");
}
}
void ds18b20_init_solo(DS18B20_Info * ds18b20_info, const OneWireBus * bus)
{
if (ds18b20_info != NULL)
{
_init(ds18b20_info, bus);
ds18b20_info->solo = true;
// ROM code not required
// read current resolution from device as it may not be power-on or factory default
ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info);
}
else
{
ESP_LOGE(TAG, "ds18b20_info is NULL");
}
}
void ds18b20_use_crc(DS18B20_Info * ds18b20_info, bool use_crc)
{
if (_is_init(ds18b20_info))
{
ds18b20_info->use_crc = use_crc;
ESP_LOGD(TAG, "use_crc %d", ds18b20_info->use_crc);
}
}
bool ds18b20_set_resolution(DS18B20_Info * ds18b20_info, DS18B20_RESOLUTION resolution)
{
bool result = false;
if (_is_init(ds18b20_info))
{
if (_check_resolution(ds18b20_info->resolution))
{
// read scratchpad up to and including configuration register
Scratchpad scratchpad = {0};
_read_scratchpad(ds18b20_info, &scratchpad,
offsetof(Scratchpad, configuration) - offsetof(Scratchpad, temperature) + 1);
// modify configuration register to set resolution
uint8_t value = (((resolution - 1) & 0x03) << 5) | 0x1f;
scratchpad.configuration = value;
ESP_LOGD(TAG, "configuration value 0x%02x", value);
// write bytes 2, 3 and 4 of scratchpad
result = _write_scratchpad(ds18b20_info, &scratchpad, /* verify */ true);
if (result)
{
ds18b20_info->resolution = resolution;
ESP_LOGD(TAG, "Resolution set to %d bits", (int)resolution);
}
else
{
// Resolution change failed - update the info resolution with the value read from configuration
ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info);
ESP_LOGW(TAG, "Resolution consistency lost - refreshed from device: %d", ds18b20_info->resolution);
}
}
else
{
ESP_LOGE(TAG, "Unsupported resolution %d", resolution);
}
}
return result;
}
DS18B20_RESOLUTION ds18b20_read_resolution(DS18B20_Info * ds18b20_info)
{
DS18B20_RESOLUTION resolution = DS18B20_RESOLUTION_INVALID;
if (_is_init(ds18b20_info))
{
// read scratchpad up to and including configuration register
Scratchpad scratchpad = {0};
_read_scratchpad(ds18b20_info, &scratchpad,
offsetof(Scratchpad, configuration) - offsetof(Scratchpad, temperature) + 1);
resolution = ((scratchpad.configuration >> 5) & 0x03) + DS18B20_RESOLUTION_9_BIT;
if (!_check_resolution(resolution))
{
ESP_LOGE(TAG, "invalid resolution read from device: 0x%02x", scratchpad.configuration);
resolution = DS18B20_RESOLUTION_INVALID;
}
else
{
ESP_LOGD(TAG, "Resolution read as %d", resolution);
}
}
return resolution;
}
bool ds18b20_convert(const DS18B20_Info * ds18b20_info)
{
bool result = false;
if (_is_init(ds18b20_info))
{
const OneWireBus * bus = ds18b20_info->bus;
if (_address_device(ds18b20_info))
{
// initiate a temperature measurement
owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT);
result = true;
}
else
{
ESP_LOGE(TAG, "ds18b20 device not responding");
}
}
return result;
}
void ds18b20_convert_all(const OneWireBus * bus)
{
if (bus)
{
bool is_present = false;
owb_reset(bus, &is_present);
owb_write_byte(bus, OWB_ROM_SKIP);
owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT);
owb_set_strong_pullup(bus, true);
}
else
{
ESP_LOGE(TAG, "bus is NULL");
}
}
float ds18b20_wait_for_conversion(const DS18B20_Info * ds18b20_info)
{
float elapsed_time = 0.0f;
if (_is_init(ds18b20_info))
{
if (ds18b20_info->bus->use_parasitic_power)
{
// in parasitic mode, devices cannot signal when they are complete,
// so use the datasheet values to wait for a duration.
elapsed_time = _wait_for_duration(ds18b20_info->resolution);
}
else
{
// wait for the device(s) to indicate the conversion is complete
elapsed_time = _wait_for_device_signal(ds18b20_info);
}
}
return elapsed_time;
}
DS18B20_ERROR ds18b20_read_temp(const DS18B20_Info * ds18b20_info, float * value)
{
DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN;
if (_is_init(ds18b20_info))
{
uint8_t temp_LSB = 0x00;
uint8_t temp_MSB = 0x80;
Scratchpad scratchpad = {0};
if ((err = _read_scratchpad(ds18b20_info, &scratchpad, 2)) == DS18B20_OK)
{
temp_LSB = scratchpad.temperature[0];
temp_MSB = scratchpad.temperature[1];
}
// https://github.com/cpetrich/counterfeit_DS18B20#solution-to-the-85-c-problem
if (scratchpad.reserved[1] == 0x0c && temp_MSB == 0x05 && temp_LSB == 0x50)
{
ESP_LOGE(TAG, "Read power-on value (85.0)");
err = DS18B20_ERROR_DEVICE;
}
float temp = _decode_temp(temp_LSB, temp_MSB, ds18b20_info->resolution);
ESP_LOGD(TAG, "temp_LSB 0x%02x, temp_MSB 0x%02x, temp %f", temp_LSB, temp_MSB, temp);
if (value)
{
*value = temp;
}
}
return err;
}
DS18B20_ERROR ds18b20_convert_and_read_temp(const DS18B20_Info * ds18b20_info, float * value)
{
DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN;
if (_is_init(ds18b20_info))
{
if (ds18b20_convert(ds18b20_info))
{
// wait at least maximum conversion time
ds18b20_wait_for_conversion(ds18b20_info);
if (value)
{
*value = 0.0f;
err = ds18b20_read_temp(ds18b20_info, value);
}
else
{
err = DS18B20_ERROR_NULL;
}
}
}
return err;
}
DS18B20_ERROR ds18b20_check_for_parasite_power(const OneWireBus * bus, bool * present)
{
DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN;
ESP_LOGD(TAG, "ds18b20_check_for_parasite_power");
if (bus) {
bool reset_present;
if ((err = owb_reset(bus, &reset_present)) == DS18B20_OK)
{
ESP_LOGD(TAG, "owb_reset OK");
if ((err = owb_write_byte(bus, OWB_ROM_SKIP)) == DS18B20_OK)
{
ESP_LOGD(TAG, "owb_write_byte(ROM_SKIP) OK");
if ((err = owb_write_byte(bus, DS18B20_FUNCTION_POWER_SUPPLY_READ)) == DS18B20_OK)
{
// Parasitic-powered devices will pull the bus low during read time slot
ESP_LOGD(TAG, "owb_write_byte(POWER_SUPPLY_READ) OK");
uint8_t value = 0;
if ((err = owb_read_bit(bus, &value)) == DS18B20_OK)
{
ESP_LOGD(TAG, "owb_read_bit OK: 0x%02x", value);
if (present)
{
*present = !(bool)(value & 0x01u);
}
}
}
}
}
}
else
{
ESP_LOGE(TAG, "bus is NULL");
err = DS18B20_ERROR_NULL;
}
return err;
}

@ -0,0 +1,206 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file ds18b20.h
* @brief Interface definitions for the Maxim Integrated DS18B20 Programmable
* Resolution 1-Wire Digital Thermometer device.
*
* This component provides structures and functions that are useful for communicating
* with DS18B20 devices connected via a Maxim Integrated 1-Wire® bus.
*/
#ifndef DS18B20_H
#define DS18B20_H
#include "owb.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Success and error codes.
*/
typedef enum
{
DS18B20_ERROR_UNKNOWN = -1, ///< An unknown error occurred, or the value was not set
DS18B20_OK = 0, ///< Success
DS18B20_ERROR_DEVICE, ///< A device error occurred
DS18B20_ERROR_CRC, ///< A CRC error occurred
DS18B20_ERROR_OWB, ///< A One Wire Bus error occurred
DS18B20_ERROR_NULL, ///< A parameter or value is NULL
} DS18B20_ERROR;
/**
* @brief Symbols for the supported temperature resolution of the device.
*/
typedef enum
{
DS18B20_RESOLUTION_INVALID = -1, ///< Invalid resolution
DS18B20_RESOLUTION_9_BIT = 9, ///< 9-bit resolution, LSB bits 2,1,0 undefined
DS18B20_RESOLUTION_10_BIT = 10, ///< 10-bit resolution, LSB bits 1,0 undefined
DS18B20_RESOLUTION_11_BIT = 11, ///< 11-bit resolution, LSB bit 0 undefined
DS18B20_RESOLUTION_12_BIT = 12, ///< 12-bit resolution (default)
} DS18B20_RESOLUTION;
/**
* @brief Structure containing information related to a single DS18B20 device connected
* via a 1-Wire bus.
*/
typedef struct
{
bool init; ///< True if struct has been initialised, otherwise false
bool solo; ///< True if device is intended to be the only one connected to the bus, otherwise false
bool use_crc; ///< True if CRC checks are to be used when retrieving information from a device on the bus
const OneWireBus * bus; ///< Pointer to 1-Wire bus information relevant to this device
OneWireBus_ROMCode rom_code; ///< The ROM code used to address this device on the bus
DS18B20_RESOLUTION resolution; ///< Temperature measurement resolution per reading
} DS18B20_Info;
/**
* @brief Construct a new device info instance.
* New instance should be initialised before calling other functions.
* @return Pointer to new device info instance, or NULL if it cannot be created.
*/
DS18B20_Info * ds18b20_malloc(void);
/**
* @brief Delete an existing device info instance.
* @param[in] ds18b20_info Pointer to device info instance.
* @param[in,out] ds18b20_info Pointer to device info instance that will be freed and set to NULL.
*/
void ds18b20_free(DS18B20_Info ** ds18b20_info);
/**
* @brief Initialise a device info instance with the specified GPIO.
* @param[in] ds18b20_info Pointer to device info instance.
* @param[in] bus Pointer to initialised 1-Wire bus instance.
* @param[in] rom_code Device-specific ROM code to identify a device on the bus.
*/
void ds18b20_init(DS18B20_Info * ds18b20_info, const OneWireBus * bus, OneWireBus_ROMCode rom_code);
/**
* @brief Initialise a device info instance as a solo device on the bus.
*
* This is subject to the requirement that this device is the ONLY device on the bus.
* This allows for faster commands to be used without ROM code addressing.
*
* NOTE: if additional devices are added to the bus, operation will cease to work correctly.
*
* @param[in] ds18b20_info Pointer to device info instance.
* @param[in] bus Pointer to initialised 1-Wire bus instance.
*/
void ds18b20_init_solo(DS18B20_Info * ds18b20_info, const OneWireBus * bus);
/**
* @brief Enable or disable use of CRC checks on device communications.
* @param[in] ds18b20_info Pointer to device info instance.
* @param[in] use_crc True to enable CRC checks, false to disable.
*/
void ds18b20_use_crc(DS18B20_Info * ds18b20_info, bool use_crc);
/**
* @brief Set temperature measurement resolution.
*
* This programs the hardware to the specified resolution and sets the cached value to be the same.
* If the program fails, the value currently in hardware is used to refresh the cache.
*
* @param[in] ds18b20_info Pointer to device info instance.
* @param[in] resolution Selected resolution.
* @return True if successful, otherwise false.
*/
bool ds18b20_set_resolution(DS18B20_Info * ds18b20_info, DS18B20_RESOLUTION resolution);
/**
* @brief Update and return the current temperature measurement resolution from the device.
* @param[in] ds18b20_info Pointer to device info instance.
* @return The currently configured temperature measurement resolution.
*/
DS18B20_RESOLUTION ds18b20_read_resolution(DS18B20_Info * ds18b20_info);
/**
* @brief Read 64-bit ROM code from device - only works when there is a single device on the bus.
* @param[in] ds18b20_info Pointer to device info instance.
* @return The 64-bit value read from the device's ROM.
*/
OneWireBus_ROMCode ds18b20_read_rom(DS18B20_Info * ds18b20_info);
/**
* @brief Start a temperature measurement conversion on a single device.
* @param[in] ds18b20_info Pointer to device info instance.
*/
bool ds18b20_convert(const DS18B20_Info * ds18b20_info);
/**
* @brief Start temperature conversion on all connected devices.
*
* This should be followed by a sufficient delay to ensure all devices complete
* their conversion before the measurements are read.
* @param[in] bus Pointer to initialised bus instance.
*/
void ds18b20_convert_all(const OneWireBus * bus);
/**
* @brief Wait for the maximum conversion time according to the current resolution of the device.
* In external power mode, the device or devices can signal when conversion has completed.
* In parasitic power mode, this is not possible, so a pre-calculated delay is performed.
* @param[in] ds18b20_info Pointer to device info instance.
* @return An estimate of the time elapsed, in milliseconds. Actual elapsed time may be greater.
*/
float ds18b20_wait_for_conversion(const DS18B20_Info * ds18b20_info);
/**
* @brief Read last temperature measurement from device.
*
* This is typically called after ds18b20_start_mass_conversion(), provided enough time
* has elapsed to ensure that all devices have completed their conversions.
* @param[in] ds18b20_info Pointer to device info instance. Must be initialised first.
* @param[out] value Pointer to the measurement value returned by the device, in degrees Celsius.
* @return DS18B20_OK if read is successful, otherwise error.
*/
DS18B20_ERROR ds18b20_read_temp(const DS18B20_Info * ds18b20_info, float * value);
/**
* @brief Convert, wait and read current temperature from device.
* @param[in] ds18b20_info Pointer to device info instance. Must be initialised first.
* @param[out] value Pointer to the measurement value returned by the device, in degrees Celsius.
* @return DS18B20_OK if read is successful, otherwise error.
*/
DS18B20_ERROR ds18b20_convert_and_read_temp(const DS18B20_Info * ds18b20_info, float * value);
/**
* @brief Check OneWire bus for presence of parasitic-powered devices.
*
* @param[in] bus Pointer to initialised bus instance.
* @param[out] present Result value, true if a parasitic-powered device was detected.
* @return DS18B20_OK if check is successful, otherwise error.
*/
DS18B20_ERROR ds18b20_check_for_parasite_power(const OneWireBus * bus, bool * present);
#ifdef __cplusplus
}
#endif
#endif // DS18B20_H

@ -0,0 +1,26 @@
{
"name": "esp32-ds18b20",
"keywords": "onewire, 1-wire, bus, sensor, temperature",
"description": "ESP32-compatible C component for the Maxim Integrated DS18B20 Programmable Resolution 1-Wire Digital Thermometer.",
"repository":
{
"type": "git",
"url": "https://github.com/DavidAntliff/esp32-ds18b20.git"
},
"authors":
[
{
"name": "David Antliff",
"url": "https://github.com/DavidAntliff",
"maintainer": true
}
],
"version": "0.1",
"frameworks": "espidf",
"platforms": "espressif32",
"build": {
"flags": [
"-I include/"
]
}
}

@ -0,0 +1,39 @@
# Build and deploy doxygen documention to GitHub Pages
sudo: false
dist: trusty
# Blacklist
branches:
only:
- master
# Environment variables
env:
global:
- GH_REPO_REF: github.com/DavidAntliff/esp32-owb.git
# Install dependencies
addons:
apt:
packages:
- doxygen
- doxygen-doc
- doxygen-latex
- doxygen-gui
- graphviz
# Build the docs
script:
- cd doc
- doxygen
# Deploy using Travis-CI/GitHub Pages integration support
deploy:
provider: pages
skip-cleanup: true
local-dir: doc/html
github-token: $GITHUB_TOKEN
on:
branch: master
target-branch: gh-pages

@ -0,0 +1,5 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_SRCS "owb.c" "owb_gpio.c" "owb_rmt.c")
register_component()

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 David Antliff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,60 @@
# esp32-owb
This is a ESP32-compatible C component for the Maxim Integrated "1-Wire" protocol.
It is written and tested for version 3.0-3.3 and 4.1-beta1 of the [ESP-IDF](https://github.com/espressif/esp-idf)
environment, using the xtensa-esp32-elf toolchain (gcc version 5.2.0, crosstool-ng-1.22.0-80-g6c4433a).
Support for v2.1 is available on the [ESP-IDF_v2.1](https://github.com/DavidAntliff/esp32-owb/tree/ESP-IDF_v2.1) branch.
## Features
This library includes:
* External power supply mode.
* Parasitic power mode.
* Static (stack-based) or dynamic (malloc-based) memory model.
* No globals - support any number of 1-Wire buses simultaneously.
* 1-Wire device detection and validation, including search for multiple devices on a single bus.
* Addressing optimisation for a single (solo) device on a bus.
* 1-Wire bus operations including multi-byte read and write operations.
* CRC checks on ROM code.
This component includes two methods of bus access - delay-driven GPIO and RMT-driven slots.
The original implementation used CPU delays to construct the 1-Wire read/write timeslots
however this proved to be too unreliable. A second method, using the ESP32's RMT peripheral,
results in very accurate read/write timeslots and more reliable operation.
Therefore I highly recommend that you use the RMT driver. *The GPIO driver is deprecated and will be removed.*
See documentation for [esp32-ds18b20](https://www.github.com/DavidAntliff/esp32-ds18b20-example#parasitic-power-mode)
for further information about parasitic power mode, including strong pull-up configuration.
## Documentation
Automatically generated API documentation (doxygen) is available [here](https://davidantliff.github.io/esp32-owb/index.html).
## Source Code
The source is available from [GitHub](https://www.github.com/DavidAntliff/esp32-owb).
## License
The code in this project is licensed under the MIT license - see LICENSE for details.
## Links
* [esp32-ds18b20](https://github.com/DavidAntliff/esp32-ds18b20) - ESP32-compatible DS18B20 Digital Thermometer
component for ESP32
* [1-Wire Communication Through Software](https://www.maximintegrated.com/en/app-notes/index.mvp/id/126)
* [1-Wire Search Algorithm](https://www.maximintegrated.com/en/app-notes/index.mvp/id/187)
* [Espressif IoT Development Framework for ESP32](https://github.com/espressif/esp-idf)
## Acknowledgements
Thank you to [Chris Morgan](https://github.com/chmorgan) for his contribution of adding RMT peripheral support for more
reliable operation.
Parts of this code are based on references provided to the public domain by Maxim Integrated.
"1-Wire" is a registered trademark of Maxim Integrated.

@ -0,0 +1,3 @@
html/
latex/

File diff suppressed because it is too large Load Diff

@ -0,0 +1,336 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
* Copyright (c) 2017 Chris Morgan <chmorgan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file
* @brief Interface definitions for the 1-Wire bus component.
*
* This component provides structures and functions that are useful for communicating
* with devices connected to a Maxim Integrated 1-Wire® bus via a single GPIO.
*
* Externally powered and "parasite-powered" devices are supported.
* Please consult your device's datasheet for power requirements.
*/
#ifndef ONE_WIRE_BUS_H
#define ONE_WIRE_BUS_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
// ROM commands
#define OWB_ROM_SEARCH 0xF0 ///< Perform Search ROM cycle to identify devices on the bus
#define OWB_ROM_READ 0x33 ///< Read device ROM (single device on bus only)
#define OWB_ROM_MATCH 0x55 ///< Address a specific device on the bus by ROM
#define OWB_ROM_SKIP 0xCC ///< Address all devices on the bus simultaneously
#define OWB_ROM_SEARCH_ALARM 0xEC ///< Address all devices on the bus with a set alarm flag
#define OWB_ROM_CODE_STRING_LENGTH (17) ///< Typical length of OneWire bus ROM ID as ASCII hex string, including null terminator
#ifndef GPIO_NUM_NC
# define GPIO_NUM_NC (-1) ///< ESP-IDF prior to v4.x does not define GPIO_NUM_NC
#endif
struct owb_driver;
/**
* @brief Structure containing 1-Wire bus information relevant to a single instance.
*/
typedef struct
{
const struct _OneWireBus_Timing * timing; ///< Pointer to timing information
bool use_crc; ///< True if CRC checks are to be used when retrieving information from a device on the bus
bool use_parasitic_power; ///< True if parasitic-powered devices are expected on the bus
gpio_num_t strong_pullup_gpio; ///< Set if an external strong pull-up circuit is required
const struct owb_driver * driver; ///< Pointer to hardware driver instance
} OneWireBus;
/**
* @brief Represents a 1-Wire ROM Code. This is a sequence of eight bytes, where
* the first byte is the family number, then the following 6 bytes form the
* serial number. The final byte is the CRC8 check byte.
*/
typedef union
{
/// Provides access via field names
struct fields
{
uint8_t family[1]; ///< family identifier (1 byte, LSB - read/write first)
uint8_t serial_number[6]; ///< serial number (6 bytes)
uint8_t crc[1]; ///< CRC check byte (1 byte, MSB - read/write last)
} fields; ///< Provides access via field names
uint8_t bytes[8]; ///< Provides raw byte access
} OneWireBus_ROMCode;
/**
* @brief Represents the state of a device search on the 1-Wire bus.
*
* Pass a pointer to this structure to owb_search_first() and
* owb_search_next() to iterate through detected devices on the bus.
*/
typedef struct
{
OneWireBus_ROMCode rom_code; ///< Device ROM code
int last_discrepancy; ///< Bit index that identifies from which bit the next search discrepancy check should start
int last_family_discrepancy; ///< Bit index that identifies the last discrepancy within the first 8-bit family code of the ROM code
int last_device_flag; ///< Flag to indicate previous search was the last device detected
} OneWireBus_SearchState;
/**
* @brief Represents the result of OWB API functions.
*/
typedef enum
{
OWB_STATUS_NOT_SET = -1, ///< A status value has not been set
OWB_STATUS_OK = 0, ///< Operation succeeded
OWB_STATUS_NOT_INITIALIZED, ///< Function was passed an uninitialised variable
OWB_STATUS_PARAMETER_NULL, ///< Function was passed a null pointer
OWB_STATUS_DEVICE_NOT_RESPONDING, ///< No response received from the addressed device or devices
OWB_STATUS_CRC_FAILED, ///< CRC failed on data received from a device or devices
OWB_STATUS_TOO_MANY_BITS, ///< Attempt to write an incorrect number of bits to the One Wire Bus
OWB_STATUS_HW_ERROR ///< A hardware error occurred
} owb_status;
/** NOTE: Driver assumes that (*init) was called prior to any other methods */
struct owb_driver
{
/** Driver identification **/
const char* name;
/** Pointer to driver uninitialization function **/
owb_status (*uninitialize)(const OneWireBus * bus);
/** Pointer to driver reset functio **/
owb_status (*reset)(const OneWireBus * bus, bool *is_present);
/** NOTE: The data is shifted out of the low bits, eg. it is written in the order of lsb to msb */
owb_status (*write_bits)(const OneWireBus *bus, uint8_t out, int number_of_bits_to_write);
/** NOTE: Data is read into the high bits, eg. each bit read is shifted down before the next bit is read */
owb_status (*read_bits)(const OneWireBus *bus, uint8_t *in, int number_of_bits_to_read);
};
/// @cond ignore
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
/// @endcond
/**
* @brief call to release resources after completing use of the OneWireBus
* @param[in] bus Pointer to initialised bus instance.
* @return status
*/
owb_status owb_uninitialize(OneWireBus * bus);
/**
* @brief Enable or disable use of CRC checks on device communications.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] use_crc True to enable CRC checks, false to disable.
* @return status
*/
owb_status owb_use_crc(OneWireBus * bus, bool use_crc);
/**
* @brief Enable or disable use of parasitic power on the One Wire Bus.
* This affects how devices signal on the bus, as devices cannot
* signal by pulling the bus low when it is pulled high.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] use_parasitic_power True to enable parasitic power, false to disable.
* @return status
*/
owb_status owb_use_parasitic_power(OneWireBus * bus, bool use_parasitic_power);
/**
* @brief Enable or disable use of extra GPIO to activate strong pull-up circuit.
* This only has effect if parasitic power mode is enabled.
* signal by pulling the bus low when it is pulled high.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] gpio Set to GPIO number to use, or GPIO_NUM_NC to disable.
* @return status
*/
owb_status owb_use_strong_pullup_gpio(OneWireBus * bus, gpio_num_t gpio);
/**
* @brief Read ROM code from device - only works when there is a single device on the bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[out] rom_code the value read from the device's rom
* @return status
*/
owb_status owb_read_rom(const OneWireBus * bus, OneWireBus_ROMCode * rom_code);
/**
* @brief Verify the device specified by ROM code is present.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] rom_code ROM code to verify.
* @param[out] is_present Set to true if a device is present, false if not
* @return status
*/
owb_status owb_verify_rom(const OneWireBus * bus, OneWireBus_ROMCode rom_code, bool * is_present);
/**
* @brief Reset the 1-Wire bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[out] is_present set to true if at least one device is present on the bus
* @return status
*/
owb_status owb_reset(const OneWireBus * bus, bool * is_present);
/**
* @brief Read a single bit from the 1-Wire bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[out] out The bit value read from the bus.
* @return status
*/
owb_status owb_read_bit(const OneWireBus * bus, uint8_t * out);
/**
* @brief Read a single byte from the 1-Wire bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[out] out The byte value read from the bus (lsb only).
* @return status
*/
owb_status owb_read_byte(const OneWireBus * bus, uint8_t * out);
/**
* @brief Read a number of bytes from the 1-Wire bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[in, out] buffer Pointer to buffer to receive read data.
* @param[in] len Number of bytes to read, must not exceed length of receive buffer.
* @return status.
*/
owb_status owb_read_bytes(const OneWireBus * bus, uint8_t * buffer, unsigned int len);
/**
* @brief Write a bit to the 1-Wire bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] bit Value to write (lsb only).
* @return status
*/
owb_status owb_write_bit(const OneWireBus * bus, uint8_t bit);
/**
* @brief Write a single byte to the 1-Wire bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] data Byte value to write to bus.
* @return status
*/
owb_status owb_write_byte(const OneWireBus * bus, uint8_t data);
/**
* @brief Write a number of bytes to the 1-Wire bus.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] buffer Pointer to buffer to write data from.
* @param[in] len Number of bytes to write.
* @return status
*/
owb_status owb_write_bytes(const OneWireBus * bus, const uint8_t * buffer, size_t len);
/**
* @brief Write a ROM code to the 1-Wire bus ensuring LSB is sent first.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] rom_code ROM code to write to bus.
* @return status
*/
owb_status owb_write_rom_code(const OneWireBus * bus, OneWireBus_ROMCode rom_code);
/**
* @brief 1-Wire 8-bit CRC lookup.
* @param[in] crc Starting CRC value. Pass in prior CRC to accumulate.
* @param[in] data Byte to feed into CRC.
* @return Resultant CRC value.
* Should be zero if last byte was the CRC byte and the CRC matches.
*/
uint8_t owb_crc8_byte(uint8_t crc, uint8_t data);
/**
* @brief 1-Wire 8-bit CRC lookup with accumulation over a block of bytes.
* @param[in] crc Starting CRC value. Pass in prior CRC to accumulate.
* @param[in] data Array of bytes to feed into CRC.
* @param[in] len Length of data array in bytes.
* @return Resultant CRC value.
* Should be zero if last byte was the CRC byte and the CRC matches.
*/
uint8_t owb_crc8_bytes(uint8_t crc, const uint8_t * data, size_t len);
/**
* @brief Locates the first device on the 1-Wire bus, if present.
* @param[in] bus Pointer to initialised bus instance.
* @param[in,out] state Pointer to an existing search state structure.
* @param[out] found_device True if a device is found, false if no devices are found.
* If a device is found, the ROM Code can be obtained from the state.
* @return status
*/
owb_status owb_search_first(const OneWireBus * bus, OneWireBus_SearchState * state, bool *found_device);
/**
* @brief Locates the next device on the 1-Wire bus, if present, starting from
* the provided state. Further calls will yield additional devices, if present.
* @param[in] bus Pointer to initialised bus instance.
* @param[in,out] state Pointer to an existing search state structure.
* @param[out] found_device True if a device is found, false if no devices are found.
* If a device is found, the ROM Code can be obtained from the state.
* @return status
*/
owb_status owb_search_next(const OneWireBus * bus, OneWireBus_SearchState * state, bool *found_device);
/**
* @brief Create a string representation of a ROM code, most significant byte (CRC8) first.
* @param[in] rom_code The ROM code to convert to string representation.
* @param[out] buffer The destination for the string representation. It will be null terminated.
* @param[in] len The length of the buffer in bytes. 64-bit ROM codes require 16 characters
* to represent as a string, plus a null terminator, for 17 bytes.
* See OWB_ROM_CODE_STRING_LENGTH.
* @return pointer to the byte beyond the last byte written
*/
char * owb_string_from_rom_code(OneWireBus_ROMCode rom_code, char * buffer, size_t len);
/**
* @brief Enable or disable the strong-pullup GPIO, if configured.
* @param[in] bus Pointer to initialised bus instance.
* @param[in] enable If true, enable the external strong pull-up by setting the GPIO high.
* If false, disable the external strong pull-up by setting the GPIO low.
* @return status
*/
owb_status owb_set_strong_pullup(const OneWireBus * bus, bool enable);
#include "owb_gpio.h"
#include "owb_rmt.h"
#ifdef __cplusplus
}
#endif
#endif // ONE_WIRE_BUS_H

@ -0,0 +1,70 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
* Copyright (c) 2017 Chris Morgan <chmorgan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file
* @brief Interface definitions for the ESP32 GPIO driver used to communicate with devices
* on the One Wire Bus.
*
* @deprecated
* This driver is deprecated and may be removed at some stage. It is not recommended for use
* due to issues with imprecise timing.
*/
#pragma once
#ifndef OWB_GPIO_H
#define OWB_GPIO_H
#include "owb.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief GPIO driver information
*/
typedef struct
{
int gpio; ///< Value of the GPIO connected to the 1-Wire bus
OneWireBus bus; ///< OneWireBus instance
} owb_gpio_driver_info;
/**
* @brief Initialise the GPIO driver.
* @return OneWireBus*, pass this into the other OneWireBus public API functions
*/
OneWireBus * owb_gpio_initialize(owb_gpio_driver_info *driver_info, int gpio);
/**
* @brief Clean up after a call to owb_gpio_initialize()
*/
void owb_gpio_uninitialize(owb_gpio_driver_info *driver_info);
#ifdef __cplusplus
}
#endif
#endif // OWB_GPIO_H

@ -0,0 +1,75 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
* Copyright (c) 2017 Chris Morgan <chmorgan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file
* @brief Interface definitions for ESP32 RMT driver used to communicate with devices
* on the One Wire Bus.
*
* This is the recommended driver.
*/
#pragma once
#ifndef OWB_RMT_H
#define OWB_RMT_H
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/ringbuf.h"
#include "driver/rmt.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief RMT driver information
*/
typedef struct
{
int tx_channel; ///< RMT channel to use for TX
int rx_channel; ///< RMT channel to use for RX
RingbufHandle_t rb; ///< Ring buffer handle
int gpio; ///< OneWireBus GPIO
OneWireBus bus; ///< OneWireBus instance
} owb_rmt_driver_info;
/**
* @brief Initialise the RMT driver.
* @param[in] info Pointer to an uninitialized owb_rmt_driver_info structure.
* Note: the structure must remain in scope for the lifetime of this component.
* @param[in] gpio_num The GPIO number to use as the One Wire bus data line.
* @param[in] tx_channel The RMT channel to use for transmitting data to bus devices.
* @param[in] rx_channel the RMT channel to use for receiving data from bus devices.
* @return OneWireBus *, pass this into the other OneWireBus public API functions
*/
OneWireBus* owb_rmt_initialize(owb_rmt_driver_info * info, gpio_num_t gpio_num,
rmt_channel_t tx_channel, rmt_channel_t rx_channel);
#ifdef __cplusplus
}
#endif
#endif // OWB_RMT_H

@ -0,0 +1,30 @@
{
"name": "esp32-owb",
"keywords": "onewire, 1-wire, bus, sensor, temperature",
"description": "ESP32-compatible C component for the Maxim Integrated 1-Wire protocol.",
"repository":
{
"type": "git",
"url": "https://github.com/DavidAntliff/esp32-owb.git"
},
"authors":
[
{
"name": "David Antliff",
"url": "https://github.com/DavidAntliff",
"maintainer": true
},
{
"name": "Chris Morgan",
"url": "https://github.com/chmorgan"
}
],
"version": "0.1",
"frameworks": "espidf",
"platforms": "espressif32",
"build": {
"flags": [
"-I include/"
]
}
}

@ -0,0 +1,744 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
* Copyright (c) 2017 Chris Morgan <chmorgan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file
*/
#include <stddef.h>
#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "driver/gpio.h"
#include "owb.h"
#include "owb_gpio.h"
static const char * TAG = "owb";
static bool _is_init(const OneWireBus * bus)
{
bool ok = false;
if (bus != NULL)
{
if (bus->driver)
{
// OK
ok = true;
}
else
{
ESP_LOGE(TAG, "bus is not initialised");
}
}
else
{
ESP_LOGE(TAG, "bus is NULL");
}
return ok;
}
/**
* @brief 1-Wire 8-bit CRC lookup.
* @param[in] crc Starting CRC value. Pass in prior CRC to accumulate.
* @param[in] data Byte to feed into CRC.
* @return Resultant CRC value.
*/
static uint8_t _calc_crc(uint8_t crc, uint8_t data)
{
// https://www.maximintegrated.com/en/app-notes/index.mvp/id/27
static const uint8_t table[256] = {
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
};
return table[crc ^ data];
}
static uint8_t _calc_crc_block(uint8_t crc, const uint8_t * buffer, size_t len)
{
do
{
crc = _calc_crc(crc, *buffer++);
ESP_LOGD(TAG, "buffer 0x%02x, crc 0x%02x, len %d", (uint8_t)*(buffer - 1), (int)crc, (int)len);
}
while (--len > 0);
return crc;
}
/**
* @param[out] is_found true if a device was found, false if not
* @return status
*/
static owb_status _search(const OneWireBus * bus, OneWireBus_SearchState * state, bool * is_found)
{
// Based on https://www.maximintegrated.com/en/app-notes/index.mvp/id/187
// initialize for search
int id_bit_number = 1;
int last_zero = 0;
int rom_byte_number = 0;
uint8_t id_bit = 0;
uint8_t cmp_id_bit = 0;
uint8_t rom_byte_mask = 1;
uint8_t search_direction = 0;
bool search_result = false;
uint8_t crc8 = 0;
owb_status status = OWB_STATUS_NOT_SET;
// if the last call was not the last one
if (!state->last_device_flag)
{
// 1-Wire reset
bool is_present;
bus->driver->reset(bus, &is_present);
if (!is_present)
{
// reset the search
state->last_discrepancy = 0;
state->last_device_flag = false;
state->last_family_discrepancy = 0;
*is_found = false;
return OWB_STATUS_OK;
}
// issue the search command
bus->driver->write_bits(bus, OWB_ROM_SEARCH, 8);
// loop to do the search
do
{
id_bit = cmp_id_bit = 0;
// read a bit and its complement
bus->driver->read_bits(bus, &id_bit, 1);
bus->driver->read_bits(bus, &cmp_id_bit, 1);
// check for no devices on 1-wire (signal level is high in both bit reads)
if (id_bit && cmp_id_bit)
{
break;
}
else
{
// all devices coupled have 0 or 1
if (id_bit != cmp_id_bit)
{
search_direction = (id_bit) ? 1 : 0; // bit write value for search
}
else
{
// if this discrepancy if before the Last Discrepancy
// on a previous next then pick the same as last time
if (id_bit_number < state->last_discrepancy)
{
search_direction = ((state->rom_code.bytes[rom_byte_number] & rom_byte_mask) > 0);
}
else
{
// if equal to last pick 1, if not then pick 0
search_direction = (id_bit_number == state->last_discrepancy);
}
// if 0 was picked then record its position in LastZero
if (search_direction == 0)
{
last_zero = id_bit_number;
// check for Last discrepancy in family
if (last_zero < 9)
{
state->last_family_discrepancy = last_zero;
}
}
}
// set or clear the bit in the ROM byte rom_byte_number
// with mask rom_byte_mask
if (search_direction == 1)
{
state->rom_code.bytes[rom_byte_number] |= rom_byte_mask;
}
else
{
state->rom_code.bytes[rom_byte_number] &= ~rom_byte_mask;
}
// serial number search direction write bit
bus->driver->write_bits(bus, search_direction, 1);
// increment the byte counter id_bit_number
// and shift the mask rom_byte_mask
id_bit_number++;
rom_byte_mask <<= 1;
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
if (rom_byte_mask == 0)
{
crc8 = owb_crc8_byte(crc8, state->rom_code.bytes[rom_byte_number]); // accumulate the CRC
rom_byte_number++;
rom_byte_mask = 1;
}
}
}
while (rom_byte_number < 8); // loop until through all ROM bytes 0-7
// if the search was successful then
if (!((id_bit_number < 65) || (crc8 != 0)))
{
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
state->last_discrepancy = last_zero;
// check for last device
if (state->last_discrepancy == 0)
{
state->last_device_flag = true;
}
search_result = true;
}
}
// if no device found then reset counters so next 'search' will be like a first
if (!search_result || !state->rom_code.bytes[0])
{
state->last_discrepancy = 0;
state->last_device_flag = false;
state->last_family_discrepancy = 0;
search_result = false;
}
status = OWB_STATUS_OK;
*is_found = search_result;
return status;
}
// Public API
owb_status owb_uninitialize(OneWireBus * bus)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
bus->driver->uninitialize(bus);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_use_crc(OneWireBus * bus, bool use_crc)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
bus->use_crc = use_crc;
ESP_LOGD(TAG, "use_crc %d", bus->use_crc);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_use_parasitic_power(OneWireBus * bus, bool use_parasitic_power)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
bus->use_parasitic_power = use_parasitic_power;
ESP_LOGD(TAG, "use_parasitic_power %d", bus->use_parasitic_power);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_use_strong_pullup_gpio(OneWireBus * bus, gpio_num_t gpio)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
if (gpio != GPIO_NUM_NC) {
// The strong GPIO pull-up is only activated if parasitic-power mode is enabled
if (!bus->use_parasitic_power) {
ESP_LOGW(TAG, "Strong pull-up GPIO set with parasitic-power disabled");
}
gpio_pad_select_gpio(gpio);
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
}
else
{
gpio_reset_pin(bus->strong_pullup_gpio);
}
bus->strong_pullup_gpio = gpio;
ESP_LOGD(TAG, "use_strong_pullup_gpio %d", bus->strong_pullup_gpio);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_read_rom(const OneWireBus * bus, OneWireBus_ROMCode *rom_code)
{
owb_status status = OWB_STATUS_NOT_SET;
memset(rom_code, 0, sizeof(OneWireBus_ROMCode));
if (!bus || !rom_code)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
bool is_present;
bus->driver->reset(bus, &is_present);
if (is_present)
{
uint8_t value = OWB_ROM_READ;
bus->driver->write_bits(bus, value, 8);
owb_read_bytes(bus, rom_code->bytes, sizeof(OneWireBus_ROMCode));
if (bus->use_crc)
{
if (owb_crc8_bytes(0, rom_code->bytes, sizeof(OneWireBus_ROMCode)) != 0)
{
ESP_LOGE(TAG, "CRC failed");
memset(rom_code->bytes, 0, sizeof(OneWireBus_ROMCode));
status = OWB_STATUS_CRC_FAILED;
}
else
{
status = OWB_STATUS_OK;
}
}
else
{
status = OWB_STATUS_OK;
}
char rom_code_s[OWB_ROM_CODE_STRING_LENGTH];
owb_string_from_rom_code(*rom_code, rom_code_s, sizeof(rom_code_s));
ESP_LOGD(TAG, "rom_code %s", rom_code_s);
}
else
{
status = OWB_STATUS_DEVICE_NOT_RESPONDING;
ESP_LOGE(TAG, "ds18b20 device not responding");
}
}
return status;
}
owb_status owb_verify_rom(const OneWireBus * bus, OneWireBus_ROMCode rom_code, bool * is_present)
{
owb_status status = OWB_STATUS_NOT_SET;
bool result = false;
if (!bus || !is_present)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
OneWireBus_SearchState state = {
.rom_code = rom_code,
.last_discrepancy = 64,
.last_device_flag = false,
};
bool is_found = false;
_search(bus, &state, &is_found);
if (is_found)
{
result = true;
for (int i = 0; i < sizeof(state.rom_code.bytes) && result; ++i)
{
result = rom_code.bytes[i] == state.rom_code.bytes[i];
ESP_LOGD(TAG, "%02x %02x", rom_code.bytes[i], state.rom_code.bytes[i]);
}
is_found = result;
}
ESP_LOGD(TAG, "state.last_discrepancy %d, state.last_device_flag %d, is_found %d",
state.last_discrepancy, state.last_device_flag, is_found);
ESP_LOGD(TAG, "rom code %sfound", result ? "" : "not ");
*is_present = result;
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_reset(const OneWireBus * bus, bool * a_device_present)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus || !a_device_present)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if(!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
status = bus->driver->reset(bus, a_device_present);
}
return status;
}
owb_status owb_read_bit(const OneWireBus * bus, uint8_t * out)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus || !out)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
bus->driver->read_bits(bus, out, 1);
ESP_LOGD(TAG, "owb_read_bit: %02x", *out);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_read_byte(const OneWireBus * bus, uint8_t * out)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus || !out)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
bus->driver->read_bits(bus, out, 8);
ESP_LOGD(TAG, "owb_read_byte: %02x", *out);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_read_bytes(const OneWireBus * bus, uint8_t * buffer, unsigned int len)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus || !buffer)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
for (int i = 0; i < len; ++i)
{
uint8_t out;
bus->driver->read_bits(bus, &out, 8);
buffer[i] = out;
}
ESP_LOGD(TAG, "owb_read_bytes, len %d:", len);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, len, ESP_LOG_DEBUG);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_write_bit(const OneWireBus * bus, const uint8_t bit)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
ESP_LOGD(TAG, "owb_write_bit: %02x", bit);
bus->driver->write_bits(bus, bit & 0x01u, 1);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_write_byte(const OneWireBus * bus, uint8_t data)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
ESP_LOGD(TAG, "owb_write_byte: %02x", data);
bus->driver->write_bits(bus, data, 8);
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_write_bytes(const OneWireBus * bus, const uint8_t * buffer, size_t len)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus || !buffer)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
ESP_LOGD(TAG, "owb_write_bytes, len %d:", len);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, len, ESP_LOG_DEBUG);
for (int i = 0; i < len; i++)
{
bus->driver->write_bits(bus, buffer[i], 8);
}
status = OWB_STATUS_OK;
}
return status;
}
owb_status owb_write_rom_code(const OneWireBus * bus, OneWireBus_ROMCode rom_code)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
owb_write_bytes(bus, (uint8_t*)&rom_code, sizeof(rom_code));
status = OWB_STATUS_OK;
}
return status;
}
uint8_t owb_crc8_byte(uint8_t crc, uint8_t data)
{
return _calc_crc(crc, data);
}
uint8_t owb_crc8_bytes(uint8_t crc, const uint8_t * data, size_t len)
{
return _calc_crc_block(crc, data, len);
}
owb_status owb_search_first(const OneWireBus * bus, OneWireBus_SearchState * state, bool * found_device)
{
bool result;
owb_status status = OWB_STATUS_NOT_SET;
if (!bus || !state || !found_device)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
memset(&state->rom_code, 0, sizeof(state->rom_code));
state->last_discrepancy = 0;
state->last_family_discrepancy = 0;
state->last_device_flag = false;
_search(bus, state, &result);
status = OWB_STATUS_OK;
*found_device = result;
}
return status;
}
owb_status owb_search_next(const OneWireBus * bus, OneWireBus_SearchState * state, bool * found_device)
{
owb_status status = OWB_STATUS_NOT_SET;
bool result = false;
if (!bus || !state || !found_device)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
_search(bus, state, &result);
status = OWB_STATUS_OK;
*found_device = result;
}
return status;
}
char * owb_string_from_rom_code(OneWireBus_ROMCode rom_code, char * buffer, size_t len)
{
for (int i = sizeof(rom_code.bytes) - 1; i >= 0; i--)
{
sprintf(buffer, "%02x", rom_code.bytes[i]);
buffer += 2;
}
return buffer;
}
owb_status owb_set_strong_pullup(const OneWireBus * bus, bool enable)
{
owb_status status = OWB_STATUS_NOT_SET;
if (!bus)
{
status = OWB_STATUS_PARAMETER_NULL;
}
else if (!_is_init(bus))
{
status = OWB_STATUS_NOT_INITIALIZED;
}
else
{
if (bus->use_parasitic_power && bus->strong_pullup_gpio != GPIO_NUM_NC)
{
gpio_set_level(bus->strong_pullup_gpio, enable ? 1 : 0);
ESP_LOGD(TAG, "strong pullup GPIO %d", enable);
} // else ignore
status = OWB_STATUS_OK;
}
return status;
}

@ -0,0 +1,287 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
* Copyright (c) 2017 Chris Morgan <chmorgan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file
*/
#include <stddef.h>
#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "driver/gpio.h"
#include "owb.h"
#include "owb_gpio.h"
static const char * TAG = "owb_gpio";
// Define PHY_DEBUG to enable GPIO output around when the bus is sampled
// by the master (this library). This GPIO output makes it possible to
// validate the master's sampling using an oscilloscope.
//
// For the debug GPIO the idle state is low and made high before the 1-wire sample
// point and low again after the sample point
#undef PHY_DEBUG
#ifdef PHY_DEBUG
// Update these defines to a pin that you can access
#define PHY_DEBUG_GPIO GPIO_NUM_27
#define PHY_DEBUG_GPIO_MASK GPIO_SEL_27
#endif
/// @cond ignore
struct _OneWireBus_Timing
{
uint32_t A, B, C, D, E, F, G, H, I, J;
};
/// @endcond
// 1-Wire timing delays (standard) in microseconds.
// Labels and values are from https://www.maximintegrated.com/en/app-notes/index.mvp/id/126
static const struct _OneWireBus_Timing _StandardTiming = {
6, // A - read/write "1" master pull DQ low duration
64, // B - write "0" master pull DQ low duration
60, // C - write "1" master pull DQ high duration
10, // D - write "0" master pull DQ high duration
9, // E - read master pull DQ high duration
55, // F - complete read timeslot + 10ms recovery
0, // G - wait before reset
480, // H - master pull DQ low duration
70, // I - master pull DQ high duration
410, // J - complete presence timeslot + recovery
};
static void _us_delay(uint32_t time_us)
{
ets_delay_us(time_us);
}
/// @cond ignore
#define info_from_bus(owb) container_of(owb, owb_gpio_driver_info, bus)
/// @endcond
/**
* @brief Generate a 1-Wire reset (initialization).
* @param[in] bus Initialised bus instance.
* @param[out] is_present true if device is present, otherwise false.
* @return status
*/
static owb_status _reset(const OneWireBus * bus, bool * is_present)
{
bool present = false;
portMUX_TYPE timeCriticalMutex = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&timeCriticalMutex);
owb_gpio_driver_info *i = info_from_bus(bus);
gpio_set_direction(i->gpio, GPIO_MODE_OUTPUT);
_us_delay(bus->timing->G);
gpio_set_level(i->gpio, 0); // Drive DQ low
_us_delay(bus->timing->H);
gpio_set_direction(i->gpio, GPIO_MODE_INPUT); // Release the bus
gpio_set_level(i->gpio, 1); // Reset the output level for the next output
_us_delay(bus->timing->I);
#ifdef PHY_DEBUG
gpio_set_level(PHY_DEBUG_GPIO, 1);
#endif
int level1 = gpio_get_level(i->gpio);
#ifdef PHY_DEBUG
gpio_set_level(PHY_DEBUG_GPIO, 0);
#endif
_us_delay(bus->timing->J); // Complete the reset sequence recovery
#ifdef PHY_DEBUG
gpio_set_level(PHY_DEBUG_GPIO, 1);
#endif
int level2 = gpio_get_level(i->gpio);
#ifdef PHY_DEBUG
gpio_set_level(PHY_DEBUG_GPIO, 0);
#endif
portEXIT_CRITICAL(&timeCriticalMutex);
present = (level1 == 0) && (level2 == 1); // Sample for presence pulse from slave
ESP_LOGD(TAG, "reset: level1 0x%x, level2 0x%x, present %d", level1, level2, present);
*is_present = present;
return OWB_STATUS_OK;
}
/**
* @brief Send a 1-Wire write bit, with recovery time.
* @param[in] bus Initialised bus instance.
* @param[in] bit The value to send.
*/
static void _write_bit(const OneWireBus * bus, int bit)
{
int delay1 = bit ? bus->timing->A : bus->timing->C;
int delay2 = bit ? bus->timing->B : bus->timing->D;
owb_gpio_driver_info *i = info_from_bus(bus);
portMUX_TYPE timeCriticalMutex = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&timeCriticalMutex);
gpio_set_direction(i->gpio, GPIO_MODE_OUTPUT);
gpio_set_level(i->gpio, 0); // Drive DQ low
_us_delay(delay1);
gpio_set_level(i->gpio, 1); // Release the bus
_us_delay(delay2);
portEXIT_CRITICAL(&timeCriticalMutex);
}
/**
* @brief Read a bit from the 1-Wire bus and return the value, with recovery time.
* @param[in] bus Initialised bus instance.
*/
static int _read_bit(const OneWireBus * bus)
{
int result = 0;
owb_gpio_driver_info *i = info_from_bus(bus);
portMUX_TYPE timeCriticalMutex = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&timeCriticalMutex);
gpio_set_direction(i->gpio, GPIO_MODE_OUTPUT);
gpio_set_level(i->gpio, 0); // Drive DQ low
_us_delay(bus->timing->A);
gpio_set_direction(i->gpio, GPIO_MODE_INPUT); // Release the bus
gpio_set_level(i->gpio, 1); // Reset the output level for the next output
_us_delay(bus->timing->E);
#ifdef PHY_DEBUG
gpio_set_level(PHY_DEBUG_GPIO, 1);
#endif
int level = gpio_get_level(i->gpio);
#ifdef PHY_DEBUG
gpio_set_level(PHY_DEBUG_GPIO, 0);
#endif
_us_delay(bus->timing->F); // Complete the timeslot and 10us recovery
portEXIT_CRITICAL(&timeCriticalMutex);
result = level & 0x01;
return result;
}
/**
* @brief Write 1-Wire data byte.
* NOTE: The data is shifted out of the low bits, eg. it is written in the order of lsb to msb
* @param[in] bus Initialised bus instance.
* @param[in] data Value to write.
* @param[in] number_of_bits_to_read bits to write
*/
static owb_status _write_bits(const OneWireBus * bus, uint8_t data, int number_of_bits_to_write)
{
ESP_LOGD(TAG, "write 0x%02x", data);
for (int i = 0; i < number_of_bits_to_write; ++i)
{
_write_bit(bus, data & 0x01);
data >>= 1;
}
return OWB_STATUS_OK;
}
/**
* @brief Read 1-Wire data byte from bus.
* NOTE: Data is read into the high bits, eg. each bit read is shifted down before the next bit is read
* @param[in] bus Initialised bus instance.
* @return Byte value read from bus.
*/
static owb_status _read_bits(const OneWireBus * bus, uint8_t *out, int number_of_bits_to_read)
{
uint8_t result = 0;
for (int i = 0; i < number_of_bits_to_read; ++i)
{
result >>= 1;
if (_read_bit(bus))
{
result |= 0x80;
}
}
ESP_LOGD(TAG, "read 0x%02x", result);
*out = result;
return OWB_STATUS_OK;
}
static owb_status _uninitialize(const OneWireBus * bus)
{
// Nothing to do here for this driver_info
return OWB_STATUS_OK;
}
static const struct owb_driver gpio_function_table =
{
.name = "owb_gpio",
.uninitialize = _uninitialize,
.reset = _reset,
.write_bits = _write_bits,
.read_bits = _read_bits
};
OneWireBus* owb_gpio_initialize(owb_gpio_driver_info * driver_info, int gpio)
{
ESP_LOGD(TAG, "%s(): gpio %d\n", __func__, gpio);
driver_info->gpio = gpio;
driver_info->bus.driver = &gpio_function_table;
driver_info->bus.timing = &_StandardTiming;
driver_info->bus.strong_pullup_gpio = GPIO_NUM_NC;
// platform specific:
gpio_pad_select_gpio(driver_info->gpio);
#ifdef PHY_DEBUG
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = PHY_DEBUG_GPIO_MASK;
io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
ESP_ERROR_CHECK(gpio_config(&io_conf));
#endif
return &(driver_info->bus);
}

@ -0,0 +1,469 @@
/*
Created by Chris Morgan based on the nodemcu project driver.
Copyright 2017 Chris Morgan <chmorgan@gmail.com>
Ported to ESP32 RMT peripheral for low-level signal generation by Arnim Laeuger.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Much of the code was inspired by Derek Yerger's code, though I don't
think much of that remains. In any event that was..
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
The CRC code was excerpted and inspired by the Dallas Semiconductor
sample code bearing this copyright.
//---------------------------------------------------------------------------
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//--------------------------------------------------------------------------
*/
#include "owb.h"
#include "driver/rmt.h"
#include "driver/gpio.h"
#include "esp_log.h"
#undef OW_DEBUG
// bus reset: duration of low phase [us]
#define OW_DURATION_RESET 480
// overall slot duration
#define OW_DURATION_SLOT 75
// write 1 slot and read slot durations [us]
#define OW_DURATION_1_LOW 2
#define OW_DURATION_1_HIGH (OW_DURATION_SLOT - OW_DURATION_1_LOW)
// write 0 slot durations [us]
#define OW_DURATION_0_LOW 65
#define OW_DURATION_0_HIGH (OW_DURATION_SLOT - OW_DURATION_0_LOW)
// sample time for read slot
#define OW_DURATION_SAMPLE (15-2)
// RX idle threshold
// needs to be larger than any duration occurring during write slots
#define OW_DURATION_RX_IDLE (OW_DURATION_SLOT + 2)
// maximum number of bits that can be read or written per slot
#define MAX_BITS_PER_SLOT (8)
static const char * TAG = "owb_rmt";
#define info_of_driver(owb) container_of(owb, owb_rmt_driver_info, bus)
// flush any pending/spurious traces from the RX channel
static void onewire_flush_rmt_rx_buf(const OneWireBus * bus)
{
void * p = NULL;
size_t s = 0;
owb_rmt_driver_info * i = info_of_driver(bus);
while ((p = xRingbufferReceive(i->rb, &s, 0)))
{
ESP_LOGD(TAG, "flushing entry");
vRingbufferReturnItem(i->rb, p);
}
}
static owb_status _reset(const OneWireBus * bus, bool * is_present)
{
rmt_item32_t tx_items[1] = {0};
bool _is_present = false;
int res = OWB_STATUS_OK;
owb_rmt_driver_info * i = info_of_driver(bus);
tx_items[0].duration0 = OW_DURATION_RESET;
tx_items[0].level0 = 0;
tx_items[0].duration1 = 0;
tx_items[0].level1 = 1;
uint16_t old_rx_thresh = 0;
rmt_get_rx_idle_thresh(i->rx_channel, &old_rx_thresh);
rmt_set_rx_idle_thresh(i->rx_channel, OW_DURATION_RESET + 60);
onewire_flush_rmt_rx_buf(bus);
rmt_rx_start(i->rx_channel, true);
if (rmt_write_items(i->tx_channel, tx_items, 1, true) == ESP_OK)
{
size_t rx_size = 0;
rmt_item32_t * rx_items = (rmt_item32_t *)xRingbufferReceive(i->rb, &rx_size, 100 / portTICK_PERIOD_MS);
if (rx_items)
{
if (rx_size >= (1 * sizeof(rmt_item32_t)))
{
#ifdef OW_DEBUG
ESP_LOGI(TAG, "rx_size: %d", rx_size);
for (int i = 0; i < (rx_size / sizeof(rmt_item32_t)); i++)
{
ESP_LOGI(TAG, "i: %d, level0: %d, duration %d", i, rx_items[i].level0, rx_items[i].duration0);
ESP_LOGI(TAG, "i: %d, level1: %d, duration %d", i, rx_items[i].level1, rx_items[i].duration1);
}
#endif
// parse signal and search for presence pulse
if ((rx_items[0].level0 == 0) && (rx_items[0].duration0 >= OW_DURATION_RESET - 2))
{
if ((rx_items[0].level1 == 1) && (rx_items[0].duration1 > 0))
{
if (rx_items[1].level0 == 0)
{
_is_present = true;
}
}
}
}
vRingbufferReturnItem(i->rb, (void *)rx_items);
}
else
{
// time out occurred, this indicates an unconnected / misconfigured bus
ESP_LOGE(TAG, "rx_items == 0");
res = OWB_STATUS_HW_ERROR;
}
}
else
{
// error in tx channel
ESP_LOGE(TAG, "Error tx");
res = OWB_STATUS_HW_ERROR;
}
rmt_rx_stop(i->rx_channel);
rmt_set_rx_idle_thresh(i->rx_channel, old_rx_thresh);
*is_present = _is_present;
ESP_LOGD(TAG, "_is_present %d", _is_present);
return res;
}
static rmt_item32_t _encode_write_slot(uint8_t val)
{
rmt_item32_t item = {0};
item.level0 = 0;
item.level1 = 1;
if (val)
{
// write "1" slot
item.duration0 = OW_DURATION_1_LOW;
item.duration1 = OW_DURATION_1_HIGH;
}
else
{
// write "0" slot
item.duration0 = OW_DURATION_0_LOW;
item.duration1 = OW_DURATION_0_HIGH;
}
return item;
}
/** NOTE: The data is shifted out of the low bits, eg. it is written in the order of lsb to msb */
static owb_status _write_bits(const OneWireBus * bus, uint8_t out, int number_of_bits_to_write)
{
rmt_item32_t tx_items[MAX_BITS_PER_SLOT + 1] = {0};
owb_rmt_driver_info * info = info_of_driver(bus);
if (number_of_bits_to_write > MAX_BITS_PER_SLOT)
{
return OWB_STATUS_TOO_MANY_BITS;
}
// write requested bits as pattern to TX buffer
for (int i = 0; i < number_of_bits_to_write; i++)
{
tx_items[i] = _encode_write_slot(out & 0x01);
out >>= 1;
}
// end marker
tx_items[number_of_bits_to_write].level0 = 1;
tx_items[number_of_bits_to_write].duration0 = 0;
owb_status status = OWB_STATUS_NOT_SET;
if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_write+1, true) == ESP_OK)
{
status = OWB_STATUS_OK;
}
else
{
status = OWB_STATUS_HW_ERROR;
ESP_LOGE(TAG, "rmt_write_items() failed");
}
return status;
}
static rmt_item32_t _encode_read_slot(void)
{
rmt_item32_t item = {0};
// construct pattern for a single read time slot
item.level0 = 0;
item.duration0 = OW_DURATION_1_LOW; // shortly force 0
item.level1 = 1;
item.duration1 = OW_DURATION_1_HIGH; // release high and finish slot
return item;
}
/** NOTE: Data is read into the high bits, eg. each bit read is shifted down before the next bit is read */
static owb_status _read_bits(const OneWireBus * bus, uint8_t *in, int number_of_bits_to_read)
{
rmt_item32_t tx_items[MAX_BITS_PER_SLOT + 1] = {0};
uint8_t read_data = 0;
int res = OWB_STATUS_OK;
owb_rmt_driver_info *info = info_of_driver(bus);
if (number_of_bits_to_read > MAX_BITS_PER_SLOT)
{
ESP_LOGE(TAG, "_read_bits() OWB_STATUS_TOO_MANY_BITS");
return OWB_STATUS_TOO_MANY_BITS;
}
// generate requested read slots
for (int i = 0; i < number_of_bits_to_read; i++)
{
tx_items[i] = _encode_read_slot();
}
// end marker
tx_items[number_of_bits_to_read].level0 = 1;
tx_items[number_of_bits_to_read].duration0 = 0;
onewire_flush_rmt_rx_buf(bus);
rmt_rx_start(info->rx_channel, true);
if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_read+1, true) == ESP_OK)
{
size_t rx_size = 0;
rmt_item32_t *rx_items = (rmt_item32_t *)xRingbufferReceive(info->rb, &rx_size, 100 / portTICK_PERIOD_MS);
if (rx_items)
{
#ifdef OW_DEBUG
for (int i = 0; i < rx_size / 4; i++)
{
ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level0, rx_items[i].duration0);
ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level1, rx_items[i].duration1);
}
#endif
if (rx_size >= number_of_bits_to_read * sizeof(rmt_item32_t))
{
for (int i = 0; i < number_of_bits_to_read; i++)
{
read_data >>= 1;
// parse signal and identify logical bit
if (rx_items[i].level1 == 1)
{
if ((rx_items[i].level0 == 0) && (rx_items[i].duration0 < OW_DURATION_SAMPLE))
{
// rising edge occured before 15us -> bit 1
read_data |= 0x80;
}
}
}
read_data >>= 8 - number_of_bits_to_read;
}
vRingbufferReturnItem(info->rb, (void *)rx_items);
}
else
{
// time out occurred, this indicates an unconnected / misconfigured bus
ESP_LOGE(TAG, "rx_items == 0");
res = OWB_STATUS_HW_ERROR;
}
}
else
{
// error in tx channel
ESP_LOGE(TAG, "Error tx");
res = OWB_STATUS_HW_ERROR;
}
rmt_rx_stop(info->rx_channel);
*in = read_data;
return res;
}
static owb_status _uninitialize(const OneWireBus *bus)
{
owb_rmt_driver_info * info = info_of_driver(bus);
rmt_driver_uninstall(info->tx_channel);
rmt_driver_uninstall(info->rx_channel);
return OWB_STATUS_OK;
}
static struct owb_driver rmt_function_table =
{
.name = "owb_rmt",
.uninitialize = _uninitialize,
.reset = _reset,
.write_bits = _write_bits,
.read_bits = _read_bits
};
static owb_status _init(owb_rmt_driver_info *info, gpio_num_t gpio_num,
rmt_channel_t tx_channel, rmt_channel_t rx_channel)
{
owb_status status = OWB_STATUS_HW_ERROR;
// Ensure the RMT peripheral is not already running
// Note: if using RMT elsewhere, don't call this here, call it at the start of your program instead.
//periph_module_disable(PERIPH_RMT_MODULE);
//periph_module_enable(PERIPH_RMT_MODULE);
info->bus.driver = &rmt_function_table;
info->tx_channel = tx_channel;
info->rx_channel = rx_channel;
info->gpio = gpio_num;
#ifdef OW_DEBUG
ESP_LOGI(TAG, "RMT TX channel: %d", info->tx_channel);
ESP_LOGI(TAG, "RMT RX channel: %d", info->rx_channel);
#endif
rmt_config_t rmt_tx = {0};
rmt_tx.channel = info->tx_channel;
rmt_tx.gpio_num = gpio_num;
rmt_tx.mem_block_num = 1;
rmt_tx.clk_div = 80;
rmt_tx.tx_config.loop_en = false;
rmt_tx.tx_config.carrier_en = false;
rmt_tx.tx_config.idle_level = 1;
rmt_tx.tx_config.idle_output_en = true;
rmt_tx.rmt_mode = RMT_MODE_TX;
if (rmt_config(&rmt_tx) == ESP_OK)
{
rmt_set_source_clk(info->tx_channel, RMT_BASECLK_APB); // only APB is supported by IDF 4.2
if (rmt_driver_install(rmt_tx.channel, 0, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK)
{
rmt_config_t rmt_rx = {0};
rmt_rx.channel = info->rx_channel;
rmt_rx.gpio_num = gpio_num;
rmt_rx.clk_div = 80;
rmt_rx.mem_block_num = 1;
rmt_rx.rmt_mode = RMT_MODE_RX;
rmt_rx.rx_config.filter_en = true;
rmt_rx.rx_config.filter_ticks_thresh = 30;
rmt_rx.rx_config.idle_threshold = OW_DURATION_RX_IDLE;
if (rmt_config(&rmt_rx) == ESP_OK)
{
rmt_set_source_clk(info->rx_channel, RMT_BASECLK_APB); // only APB is supported by IDF 4.2
if (rmt_driver_install(rmt_rx.channel, 512, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK)
{
rmt_get_ringbuf_handle(info->rx_channel, &info->rb);
status = OWB_STATUS_OK;
}
else
{
ESP_LOGE(TAG, "failed to install rx driver");
}
}
else
{
status = OWB_STATUS_HW_ERROR;
ESP_LOGE(TAG, "failed to configure rx, uninstalling rmt driver on tx channel");
rmt_driver_uninstall(rmt_tx.channel);
}
}
else
{
ESP_LOGE(TAG, "failed to install tx driver");
}
}
else
{
ESP_LOGE(TAG, "failed to configure tx");
}
// attach GPIO to previous pin
if (gpio_num < 32)
{
GPIO.enable_w1ts = (0x1 << gpio_num);
}
else
{
GPIO.enable1_w1ts.data = (0x1 << (gpio_num - 32));
}
// attach RMT channels to new gpio pin
// ATTENTION: set pin for rx first since gpio_output_disable() will
// remove rmt output signal in matrix!
rmt_set_gpio(info->rx_channel, RMT_MODE_RX, gpio_num, false);
rmt_set_gpio(info->tx_channel, RMT_MODE_TX, gpio_num, false);
// force pin direction to input to enable path to RX channel
PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[gpio_num]);
// enable open drain
GPIO.pin[gpio_num].pad_driver = 1;
return status;
}
OneWireBus * owb_rmt_initialize(owb_rmt_driver_info * info, gpio_num_t gpio_num,
rmt_channel_t tx_channel, rmt_channel_t rx_channel)
{
ESP_LOGD(TAG, "%s: gpio_num: %d, tx_channel: %d, rx_channel: %d",
__func__, gpio_num, tx_channel, rx_channel);
owb_status status = _init(info, gpio_num, tx_channel, rx_channel);
if (status != OWB_STATUS_OK)
{
ESP_LOGE(TAG, "_init() failed with status %d", status);
}
info->bus.strong_pullup_gpio = GPIO_NUM_NC;
return &(info->bus);
}

@ -0,0 +1,7 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_SRCS
"src/modbus.c"
"src/pp/payload_builder.c"
"src/pp/payload_parser.c")
register_component()

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

@ -0,0 +1,103 @@
/**
* Modbus slave
*/
#ifndef MODBUS_H
#define MODBUS_H
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
// Config - TODO do this via cmake
//#define MB_SUPPORT_FC01 // READ_COILS
//#define MB_SUPPORT_FC02 // READ_DISCRETES
#define MB_SUPPORT_FC03 // READ_HOLDING_REGISTERS
#define MB_SUPPORT_FC04 // READ_INPUT_REGISTERS
//#define MB_SUPPORT_FC05 // WRITE_SINGLE_COIL
#define MB_SUPPORT_FC06 // WRITE_SINGLE_REGISTER
//#define MB_SUPPORT_FC15 // WRITE_MULTIPLE_COILS
#define MB_SUPPORT_FC16 // WRITE_MULTIPLE_REGISTERS
//#define MB_SUPPORT_FC22 // MASK_WRITE_REGISTER
//#define MB_SUPPORT_FC23 // READ_WRITE_MULTIPLE_REGISTERS
typedef enum ModbusException {
MB_EXCEPTION_OK = 0,
MB_EXCEPTION_ILLEGAL_FUNCTION = 1,
MB_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2,
MB_EXCEPTION_ILLEGAL_DATA_VALUE = 3,
MB_EXCEPTION_SLAVE_DEVICE_FAILURE = 4,
// other codes exist but are not meaningful for simple slave devices
} ModbusException_t;
typedef enum ModbusFunction {
FC01_READ_COILS = 1,
FC02_READ_DISCRETES = 2,
FC03_READ_HOLDING_REGISTERS = 3,
FC04_READ_INPUT_REGISTERS = 4,
FC05_WRITE_SINGLE_COIL = 5,
FC06_WRITE_SINGLE_REGISTER = 6,
FC15_WRITE_MULTIPLE_COILS = 15,
FC16_WRITE_MULTIPLE_REGISTERS = 16,
FC22_MASK_WRITE_REGISTER = 22,
FC23_READ_WRITE_MULTIPLE_REGISTERS = 23,
} ModbusFunction_t;
typedef enum ModbusError {
MB_OK = 0,
MB_ERROR = 1,
MB_ERR_NOTFORME = 2,
MB_ERR_NEEDMORE = 3,
MB_ERR_CHECKSUM = 4,
MB_ERR_BADPROTO = 5,
} ModbusError_t;
typedef enum ModbusProtocol {
MB_PROTO_RTU,
MB_PROTO_TCP
} ModbusProtocol_t;
typedef struct ModbusSlave ModbusSlave_t;
struct ModbusSlave {
uint8_t addr;
ModbusProtocol_t proto;
void *userData;
ModbusException_t (*startOfAccess)(ModbusSlave_t *ms, ModbusFunction_t fcx, uint8_t slave_id);
void (*endOfAccess)(ModbusSlave_t *ms);
#ifdef MB_SUPPORT_FC01
ModbusException_t (*readCoil)(ModbusSlave_t *ms, uint16_t reference, bool *value);
#endif
#ifdef MB_SUPPORT_FC02
ModbusException_t (*readDiscrete)(ModbusSlave_t *ms, uint16_t reference, bool *value);
#endif
#if defined(MB_SUPPORT_FC03) || defined(MB_SUPPORT_FC22) || defined(MB_SUPPORT_FC23)
ModbusException_t (*readHolding)(ModbusSlave_t *ms, uint16_t reference, uint16_t *value);
#endif
#ifdef MB_SUPPORT_FC04
ModbusException_t (*readInput)(ModbusSlave_t *ms, uint16_t reference, uint16_t *value);
#endif
#if defined(MB_SUPPORT_FC15) || defined(MB_SUPPORT_FC05)
ModbusException_t (*writeCoil)(ModbusSlave_t *ms, uint16_t reference, bool value);
#endif
#if defined(MB_SUPPORT_FC06) || defined(MB_SUPPORT_FC16) || defined(MB_SUPPORT_FC22) || defined(MB_SUPPORT_FC23)
ModbusException_t (*writeHolding)(ModbusSlave_t *ms, uint16_t reference, uint16_t value);
#endif
};
ModbusError_t mb_handleRequest(
ModbusSlave_t *ms,
const uint8_t *req,
size_t req_size,
uint8_t* resp,
size_t resp_capacity,
size_t *resp_size
);
#endif //MODBUS_H

@ -0,0 +1,129 @@
#ifndef PAYLOAD_BUILDER_H
#define PAYLOAD_BUILDER_H
/**
* PayloadBuilder, part of the TinyFrame utilities collection
*
* (c) Ondřej Hruška, 2014-2022. MIT license.
*
* The builder supports big and little endian which is selected when
* initializing it or by accessing the bigendian struct field.
*
* This module helps you with building payloads (not only for TinyFrame)
*
* The builder performs bounds checking and calls the provided handler when
* the requested write wouldn't fit. Use the handler to realloc / flush the buffer
* or report an error.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "type_coerce.h"
typedef struct PayloadBuilder_ PayloadBuilder;
/**
* Full buffer handler.
*
* 'needed' more bytes should be written but the end of the buffer was reached.
*
* Return true if the problem was solved (e.g. buffer was flushed and the
* 'current' pointer moved to the beginning).
*
* If false is returned, the 'ok' flag on the struct is set to false
* and all following writes are discarded.
*/
typedef bool (*pb_full_handler)(PayloadBuilder *pb, uint32_t needed);
struct PayloadBuilder_ {
uint8_t *start; //!< Pointer to the beginning of the buffer
uint8_t *current; //!< Pointer to the next byte to be read
uint8_t *end; //!< Pointer to the end of the buffer (start + length)
pb_full_handler full_handler; //!< Callback for buffer overrun
bool bigendian; //!< Flag to use big-endian parsing
bool ok; //!< Indicates that all reads were successful
};
// --- initializer helper macros ---
/** Start the builder. */
#define pb_start_e(buf, capacity, bigendian, full_handler) ((PayloadBuilder){buf, buf, (buf)+(capacity), full_handler, bigendian, 1})
/** Start the builder in big-endian mode */
#define pb_start_be(buf, capacity, full_handler) pb_start_e(buf, capacity, 1, full_handler)
/** Start the builder in little-endian mode */
#define pb_start_le(buf, capacity, full_handler) pb_start_e(buf, capacity, 0, full_handler)
/** Start the parser in little-endian mode (default) */
#define pb_start(buf, capacity, full_handler) pb_start_le(buf, capacity, full_handler)
// --- utilities ---
/** Returns true if the parser is still in OK state (not overrun) */
#define pb_ok(pb) ((pb)->ok)
/** Get already used bytes count */
#define pb_length(pb) ((pb)->current - (pb)->start)
/** Reset the current pointer to start */
#define pb_rewind(pb) do { (pb)->current = (pb)->start; } while (0)
/** save & restore position */
typedef uint8_t* pb_mark_t;
#define pb_save(pb) ((pb)->current)
#define pb_restore(pb, mark) do { (pb)->current = (mark); } while (0)
/** Write from a buffer */
bool pb_buf(PayloadBuilder *pb, const uint8_t *buf, uint32_t len);
/** Write a zero terminated string */
bool pb_string(PayloadBuilder *pb, const char *str);
/** Write uint8_t to the buffer */
bool pb_u8(PayloadBuilder *pb, uint8_t byte);
/** Write boolean to the buffer. */
static inline bool pb_bool(PayloadBuilder *pb, bool b)
{
return pb_u8(pb, (uint8_t) b);
}
/** Write uint16_t to the buffer. */
bool pb_u16(PayloadBuilder *pb, uint16_t word);
/** Write uint32_t to the buffer. */
bool pb_u32(PayloadBuilder *pb, uint32_t word);
/** Write int8_t to the buffer. */
static inline bool pb_i8(PayloadBuilder *pb, int8_t byte)
{
return pb_u8(pb, ((union conv8) {.i8 = byte}).u8);
}
/** Write char (int8_t) to the buffer. */
static inline bool pb_char(PayloadBuilder *pb, char c)
{
return pb_i8(pb, c);
}
/** Write int16_t to the buffer. */
static inline bool pb_i16(PayloadBuilder *pb, int16_t word)
{
return pb_u16(pb, ((union conv16) {.i16 = word}).u16);
}
/** Write int32_t to the buffer. */
static inline bool pb_i32(PayloadBuilder *pb, int32_t word)
{
return pb_u32(pb, ((union conv32) {.i32 = word}).u32);
}
/** Write 4-byte float to the buffer. */
static inline bool pb_float(PayloadBuilder *pb, float f)
{
return pb_u32(pb, ((union conv32) {.f32 = f}).u32);
}
#endif // PAYLOAD_BUILDER_H

@ -0,0 +1,167 @@
#ifndef PAYLOAD_PARSER_H
#define PAYLOAD_PARSER_H
/**
* PayloadParser, part of the TinyFrame utilities collection
*
* (c) Ondřej Hruška, 2016-2022. MIT license.
*
* This module helps you with parsing payloads (not only from TinyFrame).
*
* The parser supports big and little-endian which is selected when
* initializing it or by accessing the bigendian struct field.
*
* The parser performs bounds checking and calls the provided handler when
* the requested read doesn't have enough data. Use the callback to take
* appropriate action, e.g. report an error.
*
* If the handler function is not defined, the pb->ok flag is set to false
* (use this to check for success), and further reads won't have any effect
* and always result in 0 or empty array.
*/
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "type_coerce.h"
typedef struct PayloadParser_ PayloadParser;
/**
* Empty buffer handler.
*
* 'needed' more bytes should be read but the end was reached.
*
* Return true if the problem was solved (e.g. new data loaded into
* the buffer and the 'current' pointer moved to the beginning).
*
* If false is returned, the 'ok' flag on the struct is set to false
* and all following reads will fail / return 0.
*/
typedef bool (*pp_empty_handler)(PayloadParser *pp, uint32_t needed);
struct PayloadParser_ {
const uint8_t *start; //!< Pointer to the beginning of the buffer
const uint8_t *current; //!< Pointer to the next byte to be read
const uint8_t *end; //!< Pointer to the end of the buffer (start + length)
pp_empty_handler empty_handler; //!< Callback for buffer underrun
bool bigendian; //!< Flag to use big-endian parsing
bool ok; //!< Indicates that all reads were successful
};
// --- initializer helper macros ---
/** Start the parser. */
#define pp_start_e(buf, length, bigendian, empty_handler) ((PayloadParser){buf, buf, (buf)+(length), empty_handler, bigendian, 1})
/** Start the parser in big-endian mode */
#define pp_start_be(buf, length, empty_handler) pp_start_e(buf, length, 1, empty_handler)
/** Start the parser in little-endian mode */
#define pp_start_le(buf, length, empty_handler) pp_start_e(buf, length, 0, empty_handler)
/** Start the parser in little-endian mode (default) */
#define pp_start(buf, length, empty_handler) pp_start_le(buf, length, empty_handler)
// --- utilities ---
/** Get remaining length */
#define pp_remains(pp) ((pp)->end - (pp)->current)
/** Reset the current pointer to start */
#define pp_rewind(pp) do { (pp)->current = (pp)->start; } while (0)
/** save & restore position */
typedef const uint8_t* pp_mark_t;
#define pp_save(pp) ((pp)->current)
#define pp_restore(pp, mark) do { (pp)->current = (mark); } while (0)
/** Returns true if the parser is still in OK state (not overrun) */
#define pp_ok(pp) ((pp)->ok)
/** Returns true if end was reached */
#define pp_end(pp) ((pp)->end == (pp)->current)
/**
* @brief Get the remainder of the buffer.
*
* Returns NULL and sets 'length' to 0 if there are no bytes left.
*
* @param pp
* @param length : here the buffer length will be stored. NULL to do not store.
* @return the remaining portion of the input buffer
*/
const uint8_t *pp_tail(PayloadParser *pp, uint32_t *length);
/** Read uint8_t from the payload. */
uint8_t pp_u8(PayloadParser *pp);
/** Read bool from the payload. */
static inline bool pp_bool(PayloadParser *pp)
{
return pp_u8(pp) != 0;
}
/** Skip bytes */
static inline void pp_skip(PayloadParser *pp, uint32_t num)
{
pp->current += num;
}
/** Read uint16_t from the payload. */
uint16_t pp_u16(PayloadParser *pp);
/** Read uint32_t from the payload. */
uint32_t pp_u32(PayloadParser *pp);
/** Read int8_t from the payload. */
static inline int8_t pp_i8(PayloadParser *pp)
{
return ((union conv8) {.u8 = pp_u8(pp)}).i8;
}
/** Read char (int8_t) from the payload. */
static inline int8_t pp_char(PayloadParser *pp)
{
return pp_i8(pp);
}
/** Read int16_t from the payload. */
static inline int16_t pp_i16(PayloadParser *pp)
{
return ((union conv16) {.u16 = pp_u16(pp)}).i16;
}
/** Read int32_t from the payload. */
static inline int32_t pp_i32(PayloadParser *pp)
{
return ((union conv32) {.u32 = pp_u32(pp)}).i32;
}
/** Read 4-byte float from the payload. */
static inline float pp_float(PayloadParser *pp)
{
return ((union conv32) {.u32 = pp_u32(pp)}).f32;
}
/**
* Parse a zero-terminated string
*
* @param pp - parser
* @param buffer - target buffer
* @param maxlen - buffer size
* @return actual number of bytes, excluding terminator
*/
uint32_t pp_string(PayloadParser *pp, char *buffer, uint32_t maxlen);
/**
* Parse a buffer
*
* @param pp - parser
* @param buffer - target buffer
* @param maxlen - buffer size
* @return actual number of bytes, excluding terminator
*/
uint32_t pp_buf(PayloadParser *pp, uint8_t *buffer, uint32_t maxlen);
#endif // PAYLOAD_PARSER_H

@ -0,0 +1,32 @@
#ifndef TYPE_COERCE_H
#define TYPE_COERCE_H
/**
* Structs for conversion between types,
* part of the TinyFrame utilities collection
*
* (c) Ondřej Hruška, 2016-2017. MIT license.
*
* This is a support header file for PayloadParser and PayloadBuilder.
*/
#include <stdint.h>
#include <stddef.h>
union conv8 {
uint8_t u8;
int8_t i8;
};
union conv16 {
uint16_t u16;
int16_t i16;
};
union conv32 {
uint32_t u32;
int32_t i32;
float f32;
};
#endif // TYPE_COERCE_H

@ -0,0 +1,475 @@
#include <stdint.h>
#include <stdbool.h>
#include "pp/payload_parser.h"
#include "pp/payload_builder.h"
#include "modbus.h"
/**
* Function to calculate MODBUS CRC.
*
* https://github.com/starnight/MODBUS-CRC/blob/master/modbuscrc.c
**/
static uint16_t crc16_update(uint16_t crc, uint8_t a)
{
int i;
crc ^= (uint16_t) a;
for (i = 0; i < 8; ++i) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc = (crc >> 1);
}
}
return crc;
}
static inline uint16_t crc16_init()
{
return 0xFFFF;
}
/**
* Handle a modbus request
*/
ModbusError_t mb_handleRequest(
ModbusSlave_t *ms,
const uint8_t *req,
size_t req_size,
uint8_t *resp,
size_t resp_capacity,
size_t *resp_size
)
{
uint16_t txn_id, protocol_id, ref;
#ifdef MB_SUPPORT_FC22
uint16_t and_mask, or_mask;
#endif
#ifdef MB_SUPPORT_FC23
uint16_t ref2, count2;
#endif
#if defined(MB_SUPPORT_FC01) || defined(MB_SUPPORT_FC02)
bool bvalue;
#endif
#if defined(MB_SUPPORT_FC03) || defined(MB_SUPPORT_FC04) \
|| defined(MB_SUPPORT_FC16) || defined(MB_SUPPORT_FC22) || defined(MB_SUPPORT_FC23) \
|| defined(MB_SUPPORT_FC05) || defined(MB_SUPPORT_FC06)
uint16_t value;
#endif
#if defined(MB_SUPPORT_FC01) || defined(MB_SUPPORT_FC02) || defined(MB_SUPPORT_FC15)
uint8_t scratch, bytecount, bitcnt;
#endif
#if defined(MB_SUPPORT_FC01) || defined(MB_SUPPORT_FC02) || defined(MB_SUPPORT_FC03) \
|| defined(MB_SUPPORT_FC04) || defined(MB_SUPPORT_FC15) || defined(MB_SUPPORT_FC16) \
|| defined(MB_SUPPORT_FC23)
uint16_t count;
#endif
ModbusException_t exc = MB_EXCEPTION_OK;
size_t numbytes;
uint8_t fcx;
const size_t TCP_RESP_OVERHEAD = 8;
const size_t RTU_RESP_OVERHEAD = 4;
const size_t overhead = ms->proto == MB_PROTO_TCP ? TCP_RESP_OVERHEAD : RTU_RESP_OVERHEAD;
pb_mark_t resp_fc_mark = NULL, resp_len_mark = NULL, resp_pld_start_mark = NULL;
const bool tcp = ms->proto == MB_PROTO_TCP;
PayloadParser pp = pp_start_be(req, req_size, NULL);
PayloadBuilder pb = pb_start_be(resp, resp_capacity, NULL);
if (tcp) {
/* Parse header */
txn_id = pp_u16(&pp);
protocol_id = pp_u16(&pp);
pp_skip(&pp, 2); // msglen
if (protocol_id != 0) {
return MB_ERR_BADPROTO;
}
} else {
/* check CRC */
uint16_t crc = crc16_init();
for (int pos = 0; pos < req_size /* size of CRC */; pos++) {
crc = crc16_update(crc, pp_u8(&pp));
}
if (crc != 0) {
return MB_ERR_CHECKSUM;
}
pp_rewind(&pp);
}
const uint8_t slave_id = pp_u8(&pp);
/* check addressing (don't check for TCP - UnitID is used e.g. for gateway target devices) */
if (!tcp && slave_id != ms->addr) {
return MB_ERR_NOTFORME;
}
fcx = pp_u8(&pp);
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
/* start building the response */
if (tcp) {
pb_u16(&pb, txn_id);
pb_u16(&pb, protocol_id);
resp_len_mark = pb_save(&pb);
pb_u16(&pb, 0); // Placeholder for LEN
}
pb_u8(&pb, slave_id);
resp_fc_mark = pb_save(&pb);
pb_u8(&pb, fcx); // Exceptions will add 0x80 to this
resp_pld_start_mark = pb_save(&pb);
if (ms->startOfAccess) {
exc = ms->startOfAccess(ms, fcx, slave_id);
if (exc != 0) {
goto exception;
}
}
switch (fcx) {
#ifdef MB_SUPPORT_FC05
case FC05_WRITE_SINGLE_COIL:
if (!ms->writeCoil) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
ref = pp_u16(&pp);
value = pp_u16(&pp);
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
if (resp_capacity < 4 + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
if (value == 0xFF00) {
exc = ms->writeCoil(ms, ref, true);
} else {
exc = ms->writeCoil(ms, ref, false);
}
if (exc != 0) {
goto exception;
}
pb_u16(&pb, ref);
pb_u16(&pb, value);
break;
#endif
#if defined(MB_SUPPORT_FC01) || defined(MB_SUPPORT_FC02)
case FC01_READ_COILS:
case FC02_READ_DISCRETES:
/* check we have the needed function */
#ifdef MB_SUPPORT_FC01
if (fcx == FC01_READ_COILS && !ms->readCoil) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
#endif
#ifdef MB_SUPPORT_FC02
if (fcx == FC02_READ_DISCRETES && !ms->readDiscrete) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
#endif
ref = pp_u16(&pp);
count = pp_u16(&pp);
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
bytecount = (count + 7) / 8;
if (count > 255 * 8 || resp_capacity < bytecount + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
pb_u8(&pb, bytecount);
bitcnt = 0;
scratch = 0;
while (count-- > 0) {
#ifdef MB_SUPPORT_FC01
if (fcx == FC01_READ_COILS) {
exc = ms->readCoil(ms, ref++, &bvalue);
}
#endif
#ifdef MB_SUPPORT_FC02
if (fcx == FC02_READ_DISCRETES) {
exc = ms->readDiscrete(ms, ref++, &bvalue);
}
#endif
if (exc != 0) {
goto exception;
}
scratch |= ((bvalue & 1) << bitcnt);
bitcnt++;
if (bitcnt == 8) {
pb_u8(&pb, scratch);
scratch = 0;
bitcnt = 0;
}
}
if (bitcnt != 0) {
pb_u8(&pb, scratch);
}
break;
#endif
#if defined(MB_SUPPORT_FC03) || defined(MB_SUPPORT_FC04)
case FC03_READ_HOLDING_REGISTERS:
case FC04_READ_INPUT_REGISTERS:
#ifdef MB_SUPPORT_FC03
/* check we have the needed function */
if (fcx == FC03_READ_HOLDING_REGISTERS && !ms->readHolding) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
#endif
#ifdef MB_SUPPORT_FC04
if (fcx == FC04_READ_INPUT_REGISTERS && !ms->readInput) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
#endif
ref = pp_u16(&pp);
count = pp_u16(&pp);
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
if (count > 255 || resp_capacity < count * 2 + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
pb_u8(&pb, count*2);
while (count-- > 0) {
#ifdef MB_SUPPORT_FC03
if (fcx == FC03_READ_HOLDING_REGISTERS) {
exc = ms->readHolding(ms, ref++, &value);
}
#endif
#ifdef MB_SUPPORT_FC04
if (fcx == FC04_READ_INPUT_REGISTERS) {
exc = ms->readInput(ms, ref++, &value);
}
#endif
if (exc != 0) {
goto exception;
}
pb_u16(&pb, value);
}
break;
#endif
#ifdef MB_SUPPORT_FC06
case FC06_WRITE_SINGLE_REGISTER:
if (!ms->writeHolding) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
ref = pp_u16(&pp);
value = pp_u16(&pp);
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
if (resp_capacity < 4 + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
exc = ms->writeHolding(ms, ref, value);
if (exc != 0) {
goto exception;
}
pb_u16(&pb, ref);
pb_u16(&pb, value);
break;
#endif
#ifdef MB_SUPPORT_FC16
case FC16_WRITE_MULTIPLE_REGISTERS:
if (!ms->writeHolding) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
ref = pp_u16(&pp);
count = pp_u16(&pp);
pp_skip(&pp, 1); // this is always count * 2
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
if (count > 255 || resp_capacity < 4 + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
if (pp_remains(&pp) < count * 2 + (tcp ? 0 : 2)) {
return MB_ERR_NEEDMORE;
}
pb_u16(&pb, ref);
pb_u16(&pb, count);
while (count-- > 0) {
value = pp_u16(&pp);
exc = ms->writeHolding(ms, ref++, value);
if (exc != 0) {
goto exception;
}
}
break;
#endif
#ifdef MB_SUPPORT_FC15
case FC15_WRITE_MULTIPLE_COILS:
if (!ms->writeCoil) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
ref = pp_u16(&pp);
count = pp_u16(&pp);
bytecount = pp_u8(&pp);
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
if (count > 255 || resp_capacity < 4 + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
if (pp_remains(&pp) < bytecount + (tcp ? 0 : 2)) {
return MB_ERR_NEEDMORE;
}
pb_u16(&pb, ref);
pb_u16(&pb, count);
bitcnt = 8;
scratch = 0;
while (count-- > 0) {
if (bitcnt == 8) {
scratch = pp_u8(&pp);
bitcnt = 0;
}
exc = ms->writeCoil(ms, ref++, (scratch >> bitcnt) & 1);
bitcnt++;
if (exc != 0) {
goto exception;
}
}
break;
#endif
#ifdef MB_SUPPORT_FC22
case FC22_MASK_WRITE_REGISTER:
if (!ms->writeHolding || !ms->readHolding) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
ref = pp_u16(&pp);
and_mask = pp_u16(&pp);
or_mask = pp_u16(&pp);
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
if (resp_capacity < 4 + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
exc = ms->readHolding(ms, ref, &value);
if (exc != 0) {
goto exception;
}
value = (value & and_mask) | (or_mask & ~and_mask);
exc = ms->writeHolding(ms, ref, value);
if (exc != 0) {
goto exception;
}
pb_u16(&pb, ref);
pb_u16(&pb, and_mask);
pb_u16(&pb, or_mask);
break;
#endif
#ifdef MB_SUPPORT_FC23
case FC23_READ_WRITE_MULTIPLE_REGISTERS:
if (!ms->writeHolding || !ms->readHolding) {
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
// read
ref = pp_u16(&pp);
count = pp_u16(&pp);
// write
ref2 = pp_u16(&pp);
count2 = pp_u16(&pp);
pp_skip(&pp, 1); // qty of bytes
if (!pp_ok(&pp)) {
return MB_ERR_NEEDMORE;
}
if (count > 255 || count2 > 255 || resp_capacity < count * 2 + overhead) {
exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
goto exception;
}
if (pp_remains(&pp) < count2 * 2 + (tcp ? 0 : 2)) {
return MB_ERR_NEEDMORE;
}
pb_u8(&pb, 2 * count);
// First, write
while (count2-- > 0) {
value = pp_u16(&pp);
exc = ms->writeHolding(ms, ref2++, value);
if (exc != 0) {
goto exception;
}
}
// Second, read
while (count-- > 0) {
exc = ms->readHolding(ms, ref++, &value);
if (exc != 0) {
goto exception;
}
pb_u16(&pb, value);
}
break;
#endif
default:
exc = MB_EXCEPTION_ILLEGAL_FUNCTION;
goto exception;
}
goto checksum;
exception:;
*resp_fc_mark |= 0x80;
pb_restore(&pb, resp_pld_start_mark);
pb_u8(&pb, (uint8_t) exc);
goto checksum;
checksum:
numbytes = pb_length(&pb);
if (tcp) {
pb_restore(&pb, resp_len_mark);
pb_u16(&pb, numbytes - 6);
*resp_size = numbytes;
} else {
uint8_t *m = pb.start;
uint16_t crc = crc16_init();
*resp_size = numbytes + 2;
while (numbytes > 0) {
crc = crc16_update(crc, *m++);
numbytes--;
}
pb.bigendian = 0; // CRC is sent as little endian?
pb_u16(&pb, crc);
}
if (!pb_ok(&pb)) {
return MB_ERROR;
}
if (ms->endOfAccess) {
ms->endOfAccess(ms);
}
return MB_OK;
}

@ -0,0 +1,87 @@
#include <string.h>
#include "pp/payload_builder.h"
#define pb_check_capacity(pb, needed) \
if ((pb)->current + (needed) > (pb)->end) { \
if ((pb)->full_handler == NULL || !(pb)->full_handler((pb), (needed))) { \
(pb)->ok = 0; \
} \
}
/** Write from a buffer */
bool pb_buf(PayloadBuilder *pb, const uint8_t *buf, uint32_t len)
{
pb_check_capacity(pb, len);
if (!pb->ok) { return false; }
memcpy(pb->current, buf, len);
pb->current += len;
return true;
}
/** Write s zero terminated string */
bool pb_string(PayloadBuilder *pb, const char *str)
{
uint32_t len = (uint32_t) strlen(str);
pb_check_capacity(pb, len + 1);
if (!pb->ok) {
return false;
}
memcpy(pb->current, str, len + 1);
pb->current += len + 1;
return true;
}
/** Write uint8_t to the buffer */
bool pb_u8(PayloadBuilder *pb, uint8_t byte)
{
pb_check_capacity(pb, 1);
if (!pb->ok) {
return false;
}
*pb->current++ = byte;
return true;
}
/** Write uint16_t to the buffer. */
bool pb_u16(PayloadBuilder *pb, uint16_t word)
{
pb_check_capacity(pb, 2);
if (!pb->ok) {
return false;
}
if (pb->bigendian) {
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
*pb->current++ = (uint8_t) (word & 0xFF);
} else {
*pb->current++ = (uint8_t) (word & 0xFF);
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
}
return true;
}
/** Write uint32_t to the buffer. */
bool pb_u32(PayloadBuilder *pb, uint32_t word)
{
pb_check_capacity(pb, 4);
if (!pb->ok) {
return false;
}
if (pb->bigendian) {
*pb->current++ = (uint8_t) ((word >> 24) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 16) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
*pb->current++ = (uint8_t) (word & 0xFF);
} else {
*pb->current++ = (uint8_t) (word & 0xFF);
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 16) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 24) & 0xFF);
}
return true;
}

@ -0,0 +1,104 @@
#include "pp/payload_parser.h"
#define pp_check_capacity(pp, needed) \
if ((pp)->current + (needed) > (pp)->end) { \
if ((pp)->empty_handler == NULL || !(pp)->empty_handler((pp), (needed))) { \
(pp)->ok = 0; \
} \
}
uint8_t pp_u8(PayloadParser *pp)
{
pp_check_capacity(pp, 1);
if (!pp->ok) {
return 0;
}
return *pp->current++;
}
uint16_t pp_u16(PayloadParser *pp)
{
pp_check_capacity(pp, 2);
if (!pp->ok) {
return 0;
}
uint16_t x = 0;
if (pp->bigendian) {
x |= *pp->current++ << 8;
x |= *pp->current++;
} else {
x |= *pp->current++;
x |= *pp->current++ << 8;
}
return x;
}
uint32_t pp_u32(PayloadParser *pp)
{
pp_check_capacity(pp, 4);
if (!pp->ok) {
return 0;
}
uint32_t x = 0;
if (pp->bigendian) {
x |= (uint32_t) (*pp->current++ << 24);
x |= (uint32_t) (*pp->current++ << 16);
x |= (uint32_t) (*pp->current++ << 8);
x |= *pp->current++;
} else {
x |= *pp->current++;
x |= (uint32_t) (*pp->current++ << 8);
x |= (uint32_t) (*pp->current++ << 16);
x |= (uint32_t) (*pp->current++ << 24);
}
return x;
}
const uint8_t *pp_tail(PayloadParser *pp, uint32_t *length)
{
int32_t len = (int) (pp->end - pp->current);
if (!pp->ok || len <= 0) {
if (length != NULL) {
*length = 0;
}
return NULL;
}
if (length != NULL) {
*length = (uint32_t) len;
}
return pp->current;
}
/** Read a zstring */
uint32_t pp_string(PayloadParser *pp, char *buffer, uint32_t maxlen)
{
pp_check_capacity(pp, 1);
uint32_t len = 0;
while (len < maxlen - 1 && pp->current != pp->end) {
char c = *buffer++ = (char) *pp->current++;
if (c == 0) {
break;
}
len++;
}
*buffer = 0;
return len;
}
/** Read a buffer */
uint32_t pp_buf(PayloadParser *pp, uint8_t *buffer, uint32_t maxlen)
{
uint32_t len = 0;
while (len < maxlen && pp->current != pp->end) {
*buffer++ = *pp->current++;
len++;
}
return len;
}

@ -0,0 +1,4 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_SRCS "src/ping.c")
register_component()

@ -0,0 +1 @@
ICMP ping implementation for connectivity testing

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

@ -0,0 +1,58 @@
/**
* Ping library, used to test connectivity
*/
#ifndef _PING_H
#define _PING_H
#include "lwip/ip.h"
#include "sdkconfig.h"
typedef void (*ping_success_print_cb_t)(int bytes, const char *ip, int seq, int elapsed_ms);
typedef void (*ping_fail_print_cb_t)(int seq);
/** Ping options */
typedef struct {
ip4_addr_t ip_addr; // dest IP addr
uint16_t count; // number of requests to send
uint16_t interval_ms; // delay between requests
uint16_t payload_size; // extra payload size
uint16_t timeout_ms; // ping timeout in ms
ping_success_print_cb_t success_cb;
ping_fail_print_cb_t fail_cb;
} ping_opts_t;
/** Ping response struct */
typedef struct {
int sockfd; // fd of the used socket, may be closed externally to abort the operation
uint16_t sent; // number of sent echo requests
uint16_t received; // number of received responses
uint16_t min_time_ms; // shortest ping
uint16_t max_time_ms; // longest ping
float loss_pt; // loss ratio in percent
} ping_result_t;
/** init all except the ip addr */
#define PING_CONFIG_DEFAULT() { \
.count = 5, \
.interval_ms = 1000, \
.payload_size = 32, \
.timeout_ms = 1000, \
.success_cb = NULL, \
.fail_cb = NULL, \
}
/**
* Send a ping request to a remote server.
* Parameters, including the IPv4 address, are specified in the config struct.
*
* Ping is blocking. The operation may be interrupted by closing the socket from another task,
* its FD is exposed from the beginning in result->sockfd.
*
* @param opts
* @param result - response struct, required, for storing statistics
* @return success or error
*/
esp_err_t ping(const ping_opts_t *opts, ping_result_t *result);
#endif //_PING_H

@ -0,0 +1,262 @@
// based on https://github.com/pbecchi/ESP32_ping/blob/master/Ping.cpp
#include <string.h>
#include "ping.h"
#include "esp_log.h"
#include "lwip/inet_chksum.h"
#include "lwip/ip.h"
#include "lwip/ip4.h"
#include "lwip/err.h"
#include "lwip/icmp.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
static const char *TAG = "ping";
typedef struct {
ping_opts_t config;
int sockfd;
uint16_t ping_seq_num; // sequence number for the next packet
uint16_t transmitted; // sent requests
uint16_t received; // received responses
uint16_t min_time_ms;
uint16_t max_time_ms;
uint16_t last_delay_ms;
} ping_session_t;
#define PING_ID 0xABCD
static void ping_prepare_echo(ping_session_t *session, struct icmp_echo_hdr *echohdr)
{
const size_t hdr_len = sizeof(struct icmp_echo_hdr);
const size_t payload_len = session->config.payload_size;
ICMPH_TYPE_SET(echohdr, ICMP_ECHO); // compatibility alias
ICMPH_CODE_SET(echohdr, 0);
echohdr->chksum = 0;
echohdr->id = PING_ID;
echohdr->seqno = htons(++session->ping_seq_num);
// the packet is longer than the header, it was malloc'd with extra space
// at the end for the payload
/* fill the rest of the buffer with dummy data */
for (size_t i = 0; i < payload_len; i++) {
((char *) echohdr)[hdr_len + i] = (char) (' ' + i);
}
echohdr->chksum = inet_chksum(echohdr, (u16_t) (payload_len + hdr_len));
}
static err_t ping_send(ping_session_t *session)
{
struct icmp_echo_hdr *echohdr; // we allocate a larger buffer to also fit a payload at the end
struct sockaddr_in addr_to;
const size_t packet_size = sizeof(struct icmp_echo_hdr) + session->config.payload_size;
ESP_LOGD(TAG, "Send ICMP ECHO req to %s", ip4addr_ntoa(&session->config.ip_addr));
echohdr = (struct icmp_echo_hdr *) mem_malloc((mem_size_t) packet_size);
if (!echohdr) {
return ERR_MEM;
}
ping_prepare_echo(session, echohdr);
addr_to.sin_len = sizeof(addr_to);
addr_to.sin_family = AF_INET;
addr_to.sin_addr.s_addr = session->config.ip_addr.addr; // ?
int ret = sendto(session->sockfd, echohdr, packet_size, 0, (struct sockaddr *) &addr_to, sizeof(addr_to));
if (ret <= 0) {
ESP_LOGE(TAG, "ping sendto err %d", ret);
}
else {
session->transmitted++;
}
free(echohdr);
return (ret > 0 ? ERR_OK : ERR_VAL);
}
static void ping_recv(ping_session_t *session)
{
char rxbuf[64];
int len;
struct sockaddr_in addr_from;
struct ip_hdr *iphdr;
struct icmp_echo_hdr *echohdr = NULL;
struct timeval begin, end;
uint64_t micros_begin, micros_end, elapsed_ms;
socklen_t fromlen = sizeof(struct sockaddr_in);
// Register begin time
gettimeofday(&begin, NULL); // FIXME this will fail if they are in different days
// Receive a response limit size to recv buffer - leftovers will be collected and discarded
while (0 < (len = recvfrom(session->sockfd, rxbuf, sizeof(rxbuf), 0, (struct sockaddr *) &addr_from, &fromlen))) {
if (len >= (int) (sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr))) {
// Register end time
gettimeofday(&end, NULL);
/// Get from IP address
ip4_addr_t fromaddr;
fromaddr.addr = addr_from.sin_addr.s_addr; // ???
// Get echo
iphdr = (struct ip_hdr *) rxbuf;
echohdr = (struct icmp_echo_hdr *) (rxbuf + (IPH_HL(iphdr) * 4));
// Print ....
if ((echohdr->id == PING_ID) && (echohdr->seqno == htons(session->ping_seq_num))) {
session->received++;
// Get elapsed time in milliseconds
micros_begin = (uint64_t) begin.tv_sec * 1000000;
micros_begin += begin.tv_usec;
micros_end = (uint64_t) end.tv_sec * 1000000;
micros_end += end.tv_usec;
elapsed_ms = (micros_end - micros_begin) / 1000;
session->last_delay_ms = (uint16_t) elapsed_ms;
// Update statistics
if (elapsed_ms < session->min_time_ms) {
session->min_time_ms = (uint16_t) elapsed_ms;
}
if (elapsed_ms > session->max_time_ms) {
session->max_time_ms = (uint16_t) elapsed_ms;
}
// Print ...
int seq = ntohs(echohdr->seqno);
const char *ipa = ip4addr_ntoa(&fromaddr);
ESP_LOGD(TAG, "Rx %d bytes from %s: icmp_seq=%d time=%d ms", len, ipa, seq, (int) elapsed_ms);
if (session->config.success_cb) {
session->config.success_cb(len, ipa, seq, (int) elapsed_ms);
}
return;
}
else {
// junk, ignore
ESP_LOGD(TAG, "Rx %d bytes from %s: junk", len, ip4addr_ntoa(&fromaddr));
}
}
}
session->last_delay_ms = 0;
if (len < 0) {
if (session->config.fail_cb) {
session->config.fail_cb(session->ping_seq_num);
}
ESP_LOGW(TAG, "Request timeout for icmp_seq %d", session->ping_seq_num);
}
}
esp_err_t ping(const ping_opts_t *opts, ping_result_t *result)
{
ping_session_t session = {
.min_time_ms = UINT16_MAX,
};
if (opts == NULL) {
ESP_LOGE(TAG, "opts arg is null");
return ESP_ERR_INVALID_ARG;
}
if (result == NULL) {
ESP_LOGE(TAG, "result arg is null");
return ESP_ERR_INVALID_ARG;
}
if (opts->count == 0) {
ESP_LOGE(TAG, "ping count must be > 0");
}
memcpy(&session.config, opts, sizeof(ping_opts_t));
memset(result, 0, sizeof(ping_result_t));
// Create socket
if ((session.sockfd = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) {
ESP_LOGE(TAG, "fail to open socket for ping");
return ESP_FAIL;
}
result->sockfd = session.sockfd;
// Setup socket
struct timeval tout;
tout.tv_sec = opts->timeout_ms / 1000;
tout.tv_usec = (opts->timeout_ms % 1000) * 1000;
if (setsockopt(session.sockfd, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) {
closesocket(session.sockfd);
session.sockfd = -1;
result->sockfd = -1;
ESP_LOGE(TAG, "fail to set ping socket rx timeout");
return ESP_FAIL;
}
if (setsockopt(session.sockfd, SOL_SOCKET, SO_SNDTIMEO, &tout, sizeof(tout)) < 0) {
closesocket(session.sockfd);
session.sockfd = -1;
result->sockfd = -1;
ESP_LOGE(TAG, "fail to set ping socket tx timeout");
return ESP_FAIL;
}
ESP_LOGD(TAG, "Pinging %s: %d data bytes", ip4addr_ntoa(&opts->ip_addr), opts->payload_size);
while (session.ping_seq_num < opts->count) {
if (ping_send(&session) == ERR_OK) {
ping_recv(&session);
}
if (session.ping_seq_num < opts->count) {
// subtract the wait time from the requested wait interval
int wait_time = opts->interval_ms - session.last_delay_ms;
if (wait_time >= 0) { // if 0, just yields
vTaskDelay(wait_time / portTICK_PERIOD_MS);
}
}
}
if (session.sockfd > 0) {
closesocket(session.sockfd);
session.sockfd = -1;
result->sockfd = -1;
}
result->sent = session.transmitted;
result->received = session.received;
result->min_time_ms = session.min_time_ms;
result->max_time_ms = session.max_time_ms;
result->loss_pt = (float) ((
((float) session.transmitted - (float) session.received)
/ (float) session.transmitted
) * 100.0);
ESP_LOGD(TAG, "%d tx, %d rx, %.1f%% loss, latency min %d ms, max %d ms",
result->sent,
result->received,
result->loss_pt,
result->min_time_ms,
result->max_time_ms);
return ESP_OK;
}

@ -0,0 +1,4 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_SRCS "src/socket_server.c")
register_component()

@ -0,0 +1 @@
Generic TCP socket server that can be used e.g. for telnet

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

@ -0,0 +1,282 @@
/**
* Generic implementation of a TCP socket server.
*/
#ifndef _SOCKET_SERVER_H_
#define _SOCKET_SERVER_H_
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifndef ESP_PLATFORM
#define ESP_OK 0 /*!< esp_err_t value indicating success (no error) */
#define ESP_FAIL (-1) /*!< Generic esp_err_t code indicating failure */
#define ESP_ERR_NO_MEM 0x101 /*!< Out of memory */
#define ESP_ERR_INVALID_ARG 0x102 /*!< Invalid argument */
#define ESP_ERR_INVALID_STATE 0x103 /*!< Invalid state */
#define ESP_ERR_INVALID_SIZE 0x104 /*!< Invalid size */
#define ESP_ERR_NOT_FOUND 0x105 /*!< Requested resource not found */
#define ESP_ERR_NOT_SUPPORTED 0x106 /*!< Operation or feature not supported */
#define ESP_ERR_TIMEOUT 0x107 /*!< Operation timed out */
#else
#include <esp_err.h>
#include "sdkconfig.h"
#endif
typedef struct sockd_server *Tcpd_t;
typedef struct sockd_client *TcpdClient_t;
typedef int tcpd_err_t;
#define FD_NONE (-1)
/**
* Socket read handler
*/
typedef tcpd_err_t (*tcpd_read_fn_t)(Tcpd_t serv, TcpdClient_t client, int sockfd);
/**
* Socket open handler
*/
typedef tcpd_err_t (*tcpd_open_fn_t)(Tcpd_t serv, TcpdClient_t client);
/**
* Socket close handler
*/
typedef tcpd_err_t (*tcpd_close_fn_t)(Tcpd_t serv, TcpdClient_t client);
/**
* Function called during server shutdown to free the server context
*/
typedef void (*sockd_sctx_free_fn_t)(void * sctx);
/**
* Server config structure
*/
typedef struct tcpd_config {
uint16_t port; //!< Server port
uint16_t max_clients; //!< Max number of connected clients
bool close_lru; //!< Close the least recently used client when a new connection is received
bool start_immediately; //!< If true, start the server immediately after init, otherwise it starts paused
void *sctx; //!< Server context (arbitrary user data accessible from the callbacks)
sockd_sctx_free_fn_t sctx_free_fn; //!< Context freeing function (no-op if NULL)
tcpd_read_fn_t read_fn; //!< Callback to read data from a socket.
tcpd_open_fn_t open_fn; //!< Callback to init a new client connection. Can set the client tag or handle.
tcpd_close_fn_t close_fn; //!< Callback when a client left or is kicked. Can free the client context.
const char *task_name; //!< Server task name
uint32_t task_stack; //!< Server stack size
uint8_t task_prio; //!< Server priority
} tcpd_config_t;
#define TCPD_INIT_DEFAULT() \
{ \
.port = 23, \
.max_clients = 1, \
.close_lru = true, \
.start_immediately = true, \
\
.sctx = NULL, \
.sctx_free_fn = NULL, \
\
.read_fn = NULL, \
.open_fn = NULL, \
.close_fn = NULL, \
\
.task_name = "socksrv", \
.task_stack = 2048, \
.task_prio = 3, \
}
struct tcpd_client_iter {
Tcpd_t server;
uint16_t next;
};
/** Initializer for the client iterator */
tcpd_err_t tcpd_iter_init(struct tcpd_client_iter *iter, Tcpd_t serv);
/** Iterate active clients. Returns NULL if no more clients were found. */
TcpdClient_t tcpd_client_iter_next(struct tcpd_client_iter *iterator);
/**
* Get server context. The context was defined in the config object.
*
* @param serv - server handle
* @return server context
*/
void *tcpd_get_server_ctx(Tcpd_t serv);
/**
* Get client context, set by socksrv_set_client_ctx()
*
* @param client - client handle
* @return context object
*/
void *tcpd_get_client_ctx(TcpdClient_t client);
/**
* Set client context. If allocated, it should be freed by the client close function.
*
* @param client - client handle
* @param cctx - context object
*/
void tcpd_set_client_ctx(TcpdClient_t client, void *cctx);
/**
* Get client tag.
*
* @param client - client handle
* @return tag value
*/
uint32_t tcpd_get_client_tag(TcpdClient_t client);
/**
* Set client tag. Tag may be used alongside the client context e.g. to distinguish
* context type.
*
* @param client - client handle
* @param tag - tag value
*/
void tcpd_set_client_tag(TcpdClient_t client, uint32_t tag);
/**
* Get client IP address
*
* @param client - client
* @return address struct or NULL
*/
const struct sockaddr_in * tcpd_get_client_addr(TcpdClient_t client);
/**
* Get client FD
*
* @param client
* @return fd
*/
int tcpd_get_client_fd(TcpdClient_t client);
/**
* Kick a single client.
* This may be called even when the server is stopped.
*
* The client handle should be considered invalid after this call,
* as it may be reused for another incoming connection.
* Set it to NULL for safety.
*
* @param client - client handle (obtained e.g. as an argument in the receive function)
*/
void tcpd_kick(TcpdClient_t client);
/**
* Kick all connected clients.
* This may be called even when the server is stopped.
*
* @param serv - server handle
*/
void tcpd_kick_all(Tcpd_t serv, bool with_injected);
/* Kick clients with tag. Returns kicked count, or -1 on err */
int tcpd_kick_by_tag(Tcpd_t serv, uint32_t tag);
/** Kick clients with a given IP. Returns kicked count, or -1 on err */
int tcpd_kick_by_ip(Tcpd_t serv, const struct in_addr *addr);
/**
* Inject a client with a custom FD (e.g. STDIN, other UART socket...).
*
* Injecting STDIN will automatically use STDOUT for outgoing messages.
*
* @param server
* @param fd
* @return the client, NULL on failure
*/
TcpdClient_t tcpd_inject_client(Tcpd_t server, int fd);
/**
* Initialize and start the socket server.
*
* @param config - config struct (will be copied into the server handle, can be only on stack)
* @param handle - pointer where to store the server handle.
* @return success
*/
tcpd_err_t tcpd_init(const tcpd_config_t *config, Tcpd_t *handle);
/**
* Shutdown the server, close open sockets and free all allocated memory.
* Client contexts can be freed in the close_fn, if it was defined in server config.
* It will be called for all still open sockets.
*
* The server context will be freed using the user-provided free function (set in config)
*
* The server handle should be considered invalid after calling this function.
* The same applies to all existing client handles. Set it to NULL for safety.
*
* @param serv - server handle
*/
void tcpd_shutdown(Tcpd_t serv);
/**
* Stop the server loop. It won't accept any connection requests nor data.
* A stopped server can be resumed again.
*
* Does nothing if the server is already stopped.
*
* @param serv - server handle
*/
void tcpd_suspend(Tcpd_t serv);
/**
* Start the server after it has been stopped.
*
* Does nothing if the server is already running.
*
* The server runs immediately after init,
* so this does not need to be called to start it.
*
* @param serv - server handle
*/
void tcpd_resume(Tcpd_t serv);
/**
* Send data to all connected clients
*
* @param serv - server handle
* @param buffer - data to send
* @param len - data length; if negative, treat data as a string and use strlen()
*/
tcpd_err_t tcpd_broadcast(Tcpd_t serv, const uint8_t *data, ssize_t len);
/**
* Send a message to a single client
*
* @param client - client handle
* @param data - bytes to send
* @param len - length or -1 for strlen
* @return success
*/
tcpd_err_t tcpd_send(TcpdClient_t client, const uint8_t *data, ssize_t len);
/**
* Get client slot by FD. Returns NULL if not found.
*
* @param serv - server struct
* @param sockfd
* @return
*/
TcpdClient_t tcpd_client_by_fd(Tcpd_t serv, int sockfd);
/**
* Get client by tag. Returns NULL if not found.
*
* @param serv - server struct
* @param sockfd
* @return
*/
TcpdClient_t tcpd_client_by_tag(Tcpd_t serv, uint32_t tag);
#endif //_SOCKET_SERVER_H_

File diff suppressed because it is too large Load Diff

@ -0,0 +1,18 @@
idf_component_register(
INCLUDE_DIRS "libconsole/include"
)
set(CONSOLE_FILE_SUPPORT OFF)
set(CONSOLE_USE_FILE_IO_STREAMS OFF)
set(CONSOLE_USE_TERMIOS OFF)
set(CONSOLE_USE_MEMSTREAM OFF)
set(CONSOLE_MAX_NUM_ARGS 16)
set(CONSOLE_LINE_BUF_LEN 128)
set(CONSOLE_PROMPT_MAX_LEN 24)
set(CONSOLE_HISTORY_LEN 8)
set(CONSOLE_HAVE_CSP OFF)
set(CONSOLE_USE_CSP_COMMANDS OFF)
add_subdirectory(libconsole)
target_link_libraries(${COMPONENT_LIB} INTERFACE console argtable3)

@ -0,0 +1,54 @@
build
*.swp
.lock*
.waf*
.waf3*
waf-*/
*.o
*.d
*.pyc
.project
.cproject
~*
pdebug*
*.tar*
tags
.DS_store
# ninja files
build.ninja
rules.ninja
.ninja_deps
.ninja_log
# generated by cmake
CMakeCache.txt
*.cmake
CMakeFiles
vcom
Makefile
*.cbp
*.a
.idea/
.DS_Store
# Visual Studio clutter
_ReSharper*
*.sdf
*.suo
*.dir
*.vcxproj*
*.sln
.vs
CMakeSettings.json
Win32
x64
Debug
Release
MinSizeRel
RelWithDebInfo
*.opensdf
# this is generated when using in-tree make build
include/console/config.h

@ -0,0 +1,73 @@
cmake_minimum_required(VERSION 3.13)
project(lib-console)
add_subdirectory("lib/argtable3")
file(GLOB CONSOLE_SOURCES "src/*.c")
# Console config options
# Line buffer length
set(CONSOLE_LINE_BUF_LEN "255" CACHE STRING "Line buffer length (max command size)")
# Max number of CLI args
set(CONSOLE_MAX_NUM_ARGS "64" CACHE STRING "Max number of arguments for a command")
# Prompt buffer length
set(CONSOLE_PROMPT_MAX_LEN "32" CACHE STRING "Max prompt string length")
# CLI history length
set(CONSOLE_HISTORY_LEN "32" CACHE STRING "Console history length")
option(CONSOLE_FILE_SUPPORT "Support filesystem operations (history save/load)" ON)
option(CONSOLE_USE_FILE_IO_STREAMS "Use FILE* based console I/O" ON)
option(CONSOLE_USE_TERMIOS "Use unistd/termios to set nonblocking mode & implement bytes available check" ON)
option(CONSOLE_USE_MEMSTREAM "Allow using open_memstream() to generate command hints and report argtable errors" ON)
if(CONSOLE_USE_TERMIOS AND NOT CONSOLE_USE_FILE_IO_STREAMS)
message( FATAL_ERROR "Can't use TERMIOS without FILE_IO_STREAMS" )
endif()
option(CONSOLE_USE_FREERTOS "Use FreeRTOS" ON)
option(CONSOLE_USE_PTHREADS "Use pthreads" OFF)
# Default timeout for CSP commands
set(CONSOLE_CSP_DEF_TIMEOUT_MS "3000" CACHE STRING "Default timeout for CSP commands (milliseconds)")
option(CONSOLE_TESTING_ALLOC_FUNCS "Test the internal console_malloc etc. functions on startup (with asserts)" OFF)
configure_file(
"include/console/config.h.in"
"include/console/config.h"
)
add_library(console ${CONSOLE_SOURCES})
# Enable extra warnings
#set_target_properties(console PROPERTIES COMPILE_FLAGS "-Wall -Wextra" )
target_include_directories(console
PUBLIC "include" "${CMAKE_CURRENT_BINARY_DIR}/include" # this is where the generated config header is placed
PRIVATE "src"
)
# Link libraries
set(LIBRARIES argtable3)
if(ESP_PLATFORM)
set(CONSOLE_USE_FREERTOS ON)
set(CONSOLE_USE_PTHREADS OFF)
# special hack for ESP-IDF is needed to allow implementing extern prototypes in main
set(LIBRARIES ${LIBRARIES} idf::main)
endif()
if(NOT CONSOLE_USE_FREERTOS AND NOT CONSOLE_USE_PTHREADS)
message( FATAL_ERROR "Required either FreeRTOS or PTHREADS!" )
endif()
if(CONSOLE_USE_PTHREADS)
set(LIBRARIES ${LIBRARIES} pthread)
endif()
target_link_libraries(console ${LIBRARIES})

@ -0,0 +1 @@
Proprietary code (c) VZLU 2019-2020

@ -0,0 +1,24 @@
//
// Header with useful defines and common includes
// to use when defining console commands.
//
// This file aims to concentrate the most common includes
// and utility macros to make command definitions easier to write.
//
// Created by MightyPork on 2020/03/11.
//
#ifndef LIBCONSOLE_CMDDEF_H
#define LIBCONSOLE_CMDDEF_H
#include <stdint.h>
#include <argtable3.h>
#include "console/console.h"
#if CONSOLE_HAVE_CSP
#include <csp/csp.h>
#endif
#include "console/utils.h"
#endif //LIBCONSOLE_CMDDEF_H

@ -0,0 +1,22 @@
/**
* Console configuration file, filled by CMake
*
* Created on 2020/03/16.
*/
#ifndef LIBCONSOLE_CONFIG_H
#define LIBCONSOLE_CONFIG_H
#cmakedefine CONSOLE_LINE_BUF_LEN @CONSOLE_LINE_BUF_LEN@
#cmakedefine CONSOLE_MAX_NUM_ARGS @CONSOLE_MAX_NUM_ARGS@
#cmakedefine CONSOLE_PROMPT_MAX_LEN @CONSOLE_PROMPT_MAX_LEN@
#cmakedefine CONSOLE_HISTORY_LEN @CONSOLE_HISTORY_LEN@
#cmakedefine01 CONSOLE_FILE_SUPPORT
#cmakedefine01 CONSOLE_USE_FILE_IO_STREAMS
#cmakedefine01 CONSOLE_USE_TERMIOS
#cmakedefine01 CONSOLE_USE_MEMSTREAM
#cmakedefine01 CONSOLE_USE_FREERTOS
#cmakedefine01 CONSOLE_USE_PTHREADS
#cmakedefine01 CONSOLE_TESTING_ALLOC_FUNCS
#endif //LIBCONSOLE_CONFIG_H

@ -0,0 +1,362 @@
/**
* Console - VCOM command engine
*
* Created on 2020/02/28 by Ondrej Hruska
*
* Parts are based on the console component from esp-idf
* licensed under the Apache 2 license.
*/
#ifndef LIBCONSOLE_H
#define LIBCONSOLE_H
#include <stdio.h>
#include <console/config.h>
#include <stdint.h>
#include <stdbool.h>
typedef enum {
/* Colors */
COLOR_RESET = 0xF0,
COLOR_BLACK = 0x01,
COLOR_RED = 0x02,
COLOR_GREEN = 0x03,
COLOR_YELLOW = 0x04,
COLOR_BLUE = 0x05,
COLOR_MAGENTA = 0x06,
COLOR_CYAN = 0x07,
COLOR_WHITE = 0x08,
/* Modifiers */
COLOR_NORMAL = 0x0F,
COLOR_BOLD = 0x10,
COLOR_UNDERLINE = 0x20,
COLOR_BLINK = 0x30,
COLOR_HIDE = 0x40,
} console_color_t;
#if CONSOLE_USE_FREERTOS
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
typedef SemaphoreHandle_t console_mutex_t;
#endif
#if CONSOLE_USE_PTHREADS
#include <pthread.h>
typedef pthread_mutex_t console_mutex_t;
#endif
#if CONSOLE_USE_TERMIOS
#include <termios.h>
#endif
/**
* Console config struct
*/
struct console_config {
/**
* Timeout waiting for execution lock when handling a command.
* This should be longer than the slowest command in the system.
*/
uint32_t execution_lock_timeout_ms;
};
/**
* Macro to init the console config struct
*/
#define CONSOLE_CONFIG_DEFAULTS() { \
.execution_lock_timeout_ms = 10000, \
}
typedef struct console_config console_config_t;
struct console_ctx; // early declaration
/**
* Console context
*/
typedef struct console_ctx console_ctx_t;
/**
* Console errors - return codes
*/
enum console_err {
CONSOLE_OK = 0,
/** unspecified error */
CONSOLE_ERROR = 1,
/** Allocation failed */
CONSOLE_ERR_NO_MEM,
/** Function call not allowed (e.g. console not inited) */
CONSOLE_ERR_BAD_CALL,
/** Argument validation failed */
CONSOLE_ERR_INVALID_ARG,
/** Command not recognized */
CONSOLE_ERR_UNKNOWN_CMD,
/** Timeout */
CONSOLE_ERR_TIMEOUT,
/** IO error (file open fail, etc.) */
CONSOLE_ERR_IO,
/** Operation denied (not allowed / insufficient rights) */
CONSOLE_ERR_NOT_POSSIBLE,
/** End marker */
_CONSOLE_ERR_MAX,
};
/**
* Print string describing console error.
* In case of unknown error, the number is shown.
*
* The argument should be `enum console_err`, but any number is valid.
*/
void console_err_print_ctx(struct console_ctx *ctx, int e);
// TODO error-to-string function
typedef enum console_err console_err_t;
// early decl's
struct cmd_signature;
typedef struct cmd_signature cmd_signature_t;
/**
* Command signature, passed as the last argument to the command handler
* to perform registration.
*
* \note Fill only fields that differ from default (zeros/NULLs)
*/
struct cmd_signature {
const char* command; //!< Command name, used in invocations (filled internally, do not set)
const char* help; //!< Command help text, shown when called with -h
const char* hint; //!< Hint text, generated from argtable if hint==NULL & argtable!=NULL
bool no_history; //!< Command skips history
bool custom_args; //!< Disable argtable parsing in the handler function, will be parsed manually from argv/argc
/**
* Argtable, struct or array that must end with arg_end().
* Used by the register function and for disambiguation.
*/
void* argtable;
};
/**
* Active console context pointer, valid only when handling a console command (otherwise NULL).
*
* Used by the console printf & other IO methods.
*/
extern struct console_ctx * console_active_ctx;
/**
* Function handling a callback from the console loop.
*/
typedef void(*console_callback_t)(console_ctx_t *ctx);
/**
* Console context struct
*/
struct console_ctx {
#if CONSOLE_USE_FILE_IO_STREAMS
// Streams
FILE* in; //!< stdin fd, can be -1 if not available (running commands in non-interactive mode)
FILE* out; //!< stdout fd
#if CONSOLE_USE_TERMIOS
// original termios is stored here before entering raw mode
struct termios orig_termios;
#endif
#else
void *ioctx;
#endif //CONSOLE_USE_FILE_IO_STREAMS
#if CONSOLE_FILE_SUPPORT
char *history_file;
#endif //CONSOLE_FILE_SUPPORT
bool __internal_heap_allocated;
bool exit_allowed;
char prompt[CONSOLE_PROMPT_MAX_LEN]; //!< Prompt, can be modified by a command or `before_readline_fn`
char line_buffer[CONSOLE_LINE_BUF_LEN];
/**
* Callback fired in the command evaluation loop, each time before the prompt is shown and new line read.
* This command can print to the output streams, change prompt, shutdown console, etc.
*/
console_callback_t loop_handler;
/**
* Callback fired before the console task shuts down
*/
console_callback_t shutdown_handler;
/**
* Shutdown requested. Console will exit as soon as possible.
*/
bool exit_requested;
/**
* Interactive mode. Enables additional outputs for user convenience.
*/
bool interactive;
/**
* Enable ANSI colors
*/
bool use_colors;
/* These fields are valid only during command execution */
const char **argv; //!< The current argv
size_t argc; //!< The current argc
const cmd_signature_t *cmd; //!< Pointer to the currently executed command signature
/** Used for argument validation */
uint32_t __internal_magic;
};
#define CONSOLE_CTX_MAGIC 0x6f587468
/**
* Command handler type.
*
* @param ctx - console context, including input/output files
* @param reg - signature struct; if not NULL, init the static argtable, fill this struct, and return OK (0).
* @return status code, 0 = OK (use cons_err_t constants if possible)
*/
typedef int (*console_command_t)(console_ctx_t *ctx, struct cmd_signature *reg);
/**
* Intialize the console
*
* @param config - config pointer, NULL to use defaults
* @return status code
*/
console_err_t console_init(const console_config_t *config);
/**
* @brief Register console command
*
* If the command function is already registered, this creates an alias.
* A multi-word command automatically create a command group. The group can
* be described using a description string by calling `console_group_add()`
* - at convenience before or after the commands are registered.
*
* @param name - command name (may contain spaces for "multi-part commands")
* @param handler pointer to the command handler.
* @return status code
*/
console_err_t console_cmd_register(console_command_t handler, const char *name);
/**
* @brief Register a command group.
*
* Command groups are created automatically when used.
* This method can create a group with description, or attach a custom description
* to an existing group.
*
* @param name - group name (first word of multi-part commands)
* @param descr - description to attach, can be NULL
* @return staus code
*/
console_err_t console_group_add(const char *name, const char *descr);
/**
* Add alias to an existing command by name.
*
* @param original - original command name
* @param alias - command's alias
* @return status code
*/
console_err_t console_cmd_add_alias(const char *original, const char *alias);
/**
* Add alias by handler function
*
* @param handler - command handler
* @param alias - new name
* @return status code
*/
console_err_t console_cmd_add_alias_fn(console_command_t handler, const char *alias);
/**
* Internal error print function. Has WEAK linkage, can be overridden.
*
* This function is used to report detected bugs and should not be called
* in well-written "production code".
*
* @param msg - error message
*/
void console_internal_error_print(const char *msg);
/**
* This function is guarded by a mutex and will wait for the execution lock as
* configured in console_config.
*
* @brief Run command line
* @param[in] outf
* @param[in] inf
* @param cmdline command line (command name followed by a number of arguments)
* @param[out] pRetval return code from the command (set if command was run)
* @param[out] pCommandSig - is set to a pointer to the matched command signature, or NULL on error
* @return status code
*/
console_err_t console_handle_cmd(
console_ctx_t *ctx,
const char *cmdline,
int *pRetval,
const struct cmd_signature **pCommandSig
);
/**
* Count all registered commands
*
* @return
*/
size_t console_count_commands(void);
/**
* Create a console IO context and init it to defaults.
*
* takes stdin and stdout file descriptors, or IO context (based on config flags)
*
* In the FD variant, pass NULL as STDIN if not available.
*
* @param ctx - context, if using static alloc, NULL to allocate internally.
* @return the context, NULL if alloc or init fails
*/
console_ctx_t *console_ctx_init(
console_ctx_t *ctx,
#if CONSOLE_USE_FILE_IO_STREAMS
FILE* inf, FILE* outf
#else
void * ioctx
#endif
);
/**
* Destroy a console IO context.
*
* Make sure to release any user fields (ioctx, for example) beforehand.
*
* @attention ONLY CALL THIS IF THE CONTEXT WAS DYNAMICALLY ALLOCATED!
*
* @param[in,out] ctx - pointer to context, will be set to NULL.
*/
void console_ctx_destroy(console_ctx_t *ctx);
/**
* Console task
*
* @param[in] param - must be a valid console context (see `console_ctx_init()`)
*/
void console_task(void *param);
/**
* Variant of 'console_task' for pthreads (returns NULL)
*/
void* console_task_posix(void *param);
#include "console_io.h"
#endif //LIBCONSOLE_H

@ -0,0 +1,196 @@
/**
* Console IO functions.
*
* This header is included internally by console.h
*
* Created on 2020/04/09.
*/
#ifndef LIBCONSOLE_IO_H
#define LIBCONSOLE_IO_H
#ifndef LIBCONSOLE_H
#error Include console.h!
#endif
#include <stdarg.h>
// ------ If the FILE based IO streams option is OFF, these are extern -------
/**
* Write to console context.
*
* In command context, the more convenient "vconsole_write", "console_print", "console_println"
* and "console_printf" functions can be used instead.
*
* This function is a Linenoise write callback.
*
* Return number of characters written, -1 on error.
*/
extern int console_write_ctx(console_ctx_t *ctx, const char *text, size_t len);
/**
* Read from console context's input stream.
*
* In command context, the more convenient "vconsole_read" function and the
* "console_can_read" and "console_have_stdin" helper functions can be used instead.
*
* This is also a Linenoise read callback.
*
* Return number of characters read, -1 on error
*/
extern int console_read_ctx(console_ctx_t *ctx, char *dest, size_t count);
/**
* Check if console input stream has bytes ready.
*
* @return number of queued bytes, 0 if none, -1 on error.
*/
extern int console_can_read_ctx(console_ctx_t *ctx);
/**
* Test if console context is not NULL and has stdin stream available
*
* @return have stdin
*/
extern bool console_have_stdin_ctx(console_ctx_t *ctx);
// ----- end extern interface -----
/**
* Print zero-terminated string to to console output
*
* @param text - characters to write
* @return number of characters written, or -1 on error
*/
int console_print_ctx(console_ctx_t *ctx, const char *text);
/**
* Print zero-terminated string to console output, followed by a newline
*
* @param text - characters to write
* @return number of characters written, or -1 on error
*/
ssize_t console_println_ctx(console_ctx_t *ctx, const char *text);
/**
* Console printf
*
* @param ctx - console context
* @param color - color to use, COLOR_RESET = default
* @param format
* @param ...
* @return bytes written, or -1 on error
*/
ssize_t console_printf_ctx(console_ctx_t *ctx, console_color_t color, const char *format, ...) __attribute__((format(printf,3,4)));
/**
* Console vprintf
*
* @param ctx - console context
* @param color - color to use, COLOR_RESET = default
* @param format - format string
* @param args - varargs passed as a va_list
* @return bytes written, or -1 on error
*/
ssize_t console_vprintf_ctx(console_ctx_t *ctx, console_color_t color, const char *format, va_list args);
/**
* Write to console output
*
* @attention Can only be used within a console command context
*
* @param text - characters to write
* @param len - text length
* @return number of characters written, or -1 on error
*/
ssize_t vconsole_write(const char *text, size_t len);
// -------------------- Convenience functions -------------------------
/**
* Test if we are in the console command context.
*
* @return in command context
*/
static inline bool console_context_available(void) {
return console_active_ctx != NULL;
}
/**
* Test if we are in the console command context AND the console context has an input stream
* (input stream may be used in interactive commands)
*
* @return have stdin
*/
bool console_have_stdin(void);
/**
* Console printf. Defined as a macro to pass variadic arguments
*
* @attention Can only be used within a console command context
*
* @param format
* @param ...
* @return bytes written, or -1 on error
*/
#define console_printf(format, ...) console_printf_ctx(console_active_ctx, COLOR_RESET, format, ##__VA_ARGS__)
/**
* Console printf with colors. Defined as a macro to pass variadic arguments
*
* @attention Can only be used within a console command context
*
* @param color - from console_colors_t enum
* @param format
* @param ...
* @return bytes written, or -1 on error
*/
#define console_color_printf(color, format, ...) console_printf_ctx(console_active_ctx, color, format, ##__VA_ARGS__)
/**
* Read from console input
*
* @attention Can only be used within a console command context
*
* @param dest - destination buffer
* @param count - how many characters to read
* @return number of characters read, or -1 on error
*/
ssize_t vconsole_read(char *dest, size_t count);
/**
* Check if console input stream has bytes ready.
*
* @attention Can only be used within a console command context
*
* @return number of queued bytes, 0 if none, -1 on error.
*/
int console_can_read(void);
/**
* Print zero-terminated string to to console output
*
* @attention Can only be used within a console command context
*
* @param text - characters to write
* @return number of characters written, or -1 on error
*/
static inline int console_print(const char *text) {
return console_print_ctx(console_active_ctx, text);
}
/**
* Print zero-terminated string to console output, followed by a newline
*
* @attention Can only be used within a console command context
*
* @param text - characters to write
* @return number of characters written, or -1 on error
*/
ssize_t console_println(const char *text);
#endif //LIBCONSOLE_IO_H

@ -0,0 +1,94 @@
/**
* Prefix Match
*
* Match input value to a list of options, allowing non-ambiguous abbreviation and partial matching.
* This library was designed for command recognition in interactive consoles and command interfaces.
*
* Created on 2020/06/09 by Ondřej Hruška
*/
#ifndef _PREFIX_MATCH_H
#define _PREFIX_MATCH_H
#include <stdbool.h>
#include <stddef.h>
/** Use case-sensitive matching */
#define PREFIXMATCH_CASE_SENSITIVE 1
/** Forbid abbreviations */
#define PREFIXMATCH_NOABBREV 2
/** Allow matching fewer words, if unambiguous */
#define PREFIXMATCH_MULTI_PARTIAL 4
enum pm_test_result {
PM_TEST_NO_MATCH = 0,
PM_TEST_MATCH = 1,
PM_TEST_MATCH_MULTI_PARTIAL = 2,
};
/**
* Recognize (optionally abbreviated) input
*
* @param[in] value - tested value
* @param[in] options - options to match against
* @param[in] flags - matching options (bitmask) - accepts PREFIXMATCH_CASE_SENSITIVE and PREFIXMATCH_NOABBREV
* @return index of the matched option, -1 on mismatch or ambiguous match
*/
int prefix_match(const char *value, const char **options, int flags);
/**
* Recognize input consisting of one or more (optionally abbreviated) words
*
* @param[in] value - tested value
* @param[in] options - options to match against, multi-word options separated by the listed delimiters
* @param[in] delims - string with a list of possible delimiters (like for strtok)
* @param[in] flags - matching options (bitmask) - accepts all options
* @return index of the matched option, -1 on mismatch or ambiguous match
*/
int prefix_multipart_match(const char *restrict value, const char **options, const char* restrict delims, int flags);
// useful internal functions exported for possible re-use
/**
* Test if two word sentences match, with individual words optionally allowed to be abbreviated.
*
* @internal
* @param[in] tested - tested (optionally abbreviated) sentence
* @param[in] full - full sentence
* @param[in] delims - list of possible delimiters, same may be used for both sentences
* @param[in] flags - matching options (bitmask) - accepts all options
* @return 1-match; 0-no match; 2-partial (some words) match, if the PREFIXMATCH_MULTI_PARTIAL flag is set
*/
enum pm_test_result prefix_multipart_test(const char *restrict tested, const char* restrict full, const char *restrict delims, int flags);
/**
* Count words in a "sentence", delimited by any of the given set of delimiters.
*
* @internal
* @param[in] sentence - one or multi-word string
* @param[in] delims - delimiters accepted
* @return number of words
*/
size_t pm_count_words(const char * restrict sentence, const char * restrict delims);
/**
* Measure word length
*
* @internal
* @param[in] word - start of a word that ends with either one of the delimiters, or a null byte.
* @param[in] delims - delimiters accepted
* @return word length
*/
size_t pm_word_len(const char * restrict word, const char * restrict delims);
/**
* Skip N words in a sentence.
*
* @param[in] sentence - one or multi-word string
* @param[in] delims - delimiters accepted
* @param[in] skip - how many words to skip
* @return pointer to the first byte after the last skipped word
*/
const char *pm_skip_words(const char * restrict sentence, const char * restrict delims, size_t skip);
#endif //_PREFIX_MATCH_H

@ -0,0 +1,213 @@
/**
* Utilities for console commands
*
* Created on 2020/03/11.
*/
#ifndef LIBCONSOLE_UTILS_H
#define LIBCONSOLE_UTILS_H
#include <stdio.h>
#include <console/console.h>
#ifndef STR
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#endif
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif
#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif
#ifndef OBC_FIRMWARE
#define EXPENDABLE_STRING(x) x
#define EXPENDABLE_CODE(x) x
#else
#define EXPENDABLE_STRING(x) ""
#define EXPENDABLE_CODE(x) do {} while(0);
#endif
/**
* Read an argument, or return default if it is empty.
*
* This works for commands arg_int0
*
* Usage:
*
* \code
* static struct {
* struct arg_int *foo;
* } args;
*
* args.foo = arg_int0(...);
*
* int foo = GET_ARG_INT0(args.foo, 1234);
* \endcode
*/
#define GET_ARG_INT0(_arg, _def) ((_arg)->count ? (_arg)->ival[0] : (_def))
/**
* Get CSP node ID from an argument table, using own address as default.
*
* Usage:
*
* \code
* static struct {
* struct arg_int *node;
* } args;
*
* args.node = arg_int0(...);
*
* int node = GET_ARG_CSPADDR0(args.node);
* \endcode
*/
#define GET_ARG_CSPADDR0(_arg) GET_ARG_INT0((_arg), csp_get_address())
/**
* Shortcut to get a timeout argument's value, using CSP_DEF_TIMEOUT_MS as default.
*/
#define GET_ARG_TIMEOUT0(_arg) GET_ARG_INT0((_arg), CONSOLE_CSP_DEF_TIMEOUT_MS)
/**
* Define an optional CSP node argument
*/
#define arg_cspaddr0() arg_int0(NULL, NULL, "<node>", EXPENDABLE_STRING("node ID"))
/**
* Define a mandatory CSP node argument
*/
#define arg_cspaddr1() arg_int1(NULL, NULL, "<node>", EXPENDABLE_STRING("node ID"))
/**
* Define an optional timeout argument, with `CSP_DEF_TIMEOUT_MS`
* shown as default. Use `GET_ARG_TIMEOUT0()` to retrieve its value.
*/
#define arg_timeout0() arg_int0("t", "timeout", "<ms>", EXPENDABLE_STRING("timeout in ms (default "STR(CONSOLE_CSP_DEF_TIMEOUT_MS)")"))
/**
* Define a timeout argument with a custom value shown as default.
* Use `GET_ARG_INT0()` with the matching default to retrieve its value.
*/
#define arg_timeout0_def(_def) arg_int0("t", "timeout", "<ms>", EXPENDABLE_STRING("timeout in ms (default "STR(_def)")"))
#define EMPTY_CMD_SETUP(_helptext) \
(void)ctx; \
static struct { \
struct arg_end *end; \
} args; \
\
if (reg) { \
args.end = arg_end(1); \
\
reg->argtable = &args; \
reg->help = EXPENDABLE_STRING(_helptext); \
return 0; \
}
/**
* Hexdump a buffer
*
* @param outf - output file
* @param data - data to dump
* @param len - data size
*/
void console_hexdump(const void *data, size_t len);
/**
* Decode hexa string to binary
*
* @param hex - hexa string, upper or lower case, must have even length
* @param dest - destination buffer
* @param capacity - buffer size
* @return destination length, or: -1 (bad args), -2 (bad format), -3 (too long)
*/
int console_base16_decode(const char *hex, void *dest, size_t capacity);
#if CONSOLE_USE_MEMSTREAM
/**
* Data struct for the filecap utilities
*/
struct console_filecap {
char *buf;
size_t buf_size;
FILE *file;
};
typedef struct console_filecap console_filecap_t;
/**
* Open a temporary in-memory file that can be used to capture the output
* of functions taking a FILE * argument.
*
* @param cap - pointer to a filecap struct (can be on stack)
* @return success
*/
console_err_t console_filecap_init(console_filecap_t *cap);
/**
* Clean up the capture struct.
*
* If the buffer is to be used elsewhere, place NULL in the struct to avoid freeing it.
*
* If the struct itself was allocated on heap, it is the caller's responsibility to free it manually.
*
* @param cap - pointer to a filecap struct
*/
void console_filecap_end(console_filecap_t *cap);
/**
* Print the captured output to console output stream and clean up the struct (calls `console_filecap_end()`)
*
* @param cap - pointer to a filecap struct
*/
void console_filecap_print_end(console_filecap_t *cap);
#endif // CONSOLE_USE_MEMSTREAM
/**
* Cross-platform malloc that can be used from console commands.
* If CSP is available and the platform is not POSIX, the implementation from there is used (i.e. FreeRTOS alloc)
*/
void * __attribute__((malloc)) console_malloc(size_t size);
/**
* Cross-platform realloc that can be used from console commands.
*
* It is not possible to determine the size of an allocated memory region in a portable way,
* that's why this function takes the old size as an argument. On POSIX, the argument is simply ignored.
*
* If CSP is available and the platform is not POSIX, the implementation from there is used (i.e. FreeRTOS alloc).
* NOTE: CSP does not provide realloc, therefore this function allocates a new buffer, copies data, and frees the old buffer.
*
* Returns the original buffer if the new size is <= old size.
*/
void * console_realloc(void *ptr, size_t oldsize, size_t newsize);
/**
* Cross-platform calloc that can be used from console commands.
* If CSP is available and the platform is not POSIX, the implementation from there is used (i.e. FreeRTOS alloc)
*/
void * __attribute__((malloc,alloc_size(1,2))) console_calloc(size_t nmemb, size_t size);
/**
* `free()` for memory allocated by `console_malloc()` or `console_calloc()`
*/
void console_free(void *ptr);
/**
* `strdup()` using `console_malloc()`. Free with `console_free()`
*/
char * console_strdup(const char *ptr);
/**
* `strndup()` using `console_malloc()`. Free with `console_free()`
*/
char * console_strndup(const char *ptr, size_t maxlen);
#endif //LIBCONSOLE_UTILS_H

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.10)
project(console-argtable3)
add_library(argtable3 argtable3.c)
target_include_directories(argtable3
PUBLIC "."
)
target_link_libraries(argtable3 PRIVATE console)

@ -0,0 +1,3 @@
Copy of upstream argtable3 from github
It is released under the 3-clause BSD license.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,306 @@
/*******************************************************************************
* This file is part of the argtable3 library.
*
* Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann
* <sheitmann@users.sourceforge.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of STEWART HEITMANN nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
#ifndef ARGTABLE3
#define ARGTABLE3
#include <stdio.h> /* FILE */
#include <time.h> /* struct tm */
#ifdef __cplusplus
extern "C" {
#endif
#define ARG_REX_ICASE 1
/* bit masks for arg_hdr.flag */
enum
{
ARG_TERMINATOR=0x1,
ARG_HASVALUE=0x2,
ARG_HASOPTVALUE=0x4
};
typedef void (arg_resetfn)(void *parent);
typedef int (arg_scanfn)(void *parent, const char *argval);
typedef int (arg_checkfn)(void *parent);
typedef void (arg_errorfn)(void *parent, FILE *fp, int error, const char *argval, const char *progname);
/*
* The arg_hdr struct defines properties that are common to all arg_xxx structs.
* The argtable library requires each arg_xxx struct to have an arg_hdr
* struct as its first data member.
* The argtable library functions then use this data to identify the
* properties of the command line option, such as its option tags,
* datatype string, and glossary strings, and so on.
* Moreover, the arg_hdr struct contains pointers to custom functions that
* are provided by each arg_xxx struct which perform the tasks of parsing
* that particular arg_xxx arguments, performing post-parse checks, and
* reporting errors.
* These functions are private to the individual arg_xxx source code
* and are the pointer to them are initiliased by that arg_xxx struct's
* constructor function. The user could alter them after construction
* if desired, but the original intention is for them to be set by the
* constructor and left unaltered.
*/
struct arg_hdr
{
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
const char *shortopts; /* String defining the short options */
const char *longopts; /* String defiing the long options */
const char *datatype; /* Description of the argument data type */
const char *glossary; /* Description of the option as shown by arg_print_glossary function */
int mincount; /* Minimum number of occurences of this option accepted */
int maxcount; /* Maximum number of occurences if this option accepted */
void *parent; /* Pointer to parent arg_xxx struct */
arg_resetfn *resetfn; /* Pointer to parent arg_xxx reset function */
arg_scanfn *scanfn; /* Pointer to parent arg_xxx scan function */
arg_checkfn *checkfn; /* Pointer to parent arg_xxx check function */
arg_errorfn *errorfn; /* Pointer to parent arg_xxx error function */
void *priv; /* Pointer to private header data for use by arg_xxx functions */
};
struct arg_rem
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
};
struct arg_lit
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
};
struct arg_int
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
int *ival; /* Array of parsed argument values */
};
struct arg_dbl
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
double *dval; /* Array of parsed argument values */
};
struct arg_str
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
const char **sval; /* Array of parsed argument values */
};
struct arg_rex
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args */
const char **sval; /* Array of parsed argument values */
};
struct arg_file
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of matching command line args*/
const char **filename; /* Array of parsed filenames (eg: /home/foo.bar) */
const char **basename; /* Array of parsed basenames (eg: foo.bar) */
const char **extension; /* Array of parsed extensions (eg: .bar) */
};
struct arg_date
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
const char *format; /* strptime format string used to parse the date */
int count; /* Number of matching command line args */
struct tm *tmval; /* Array of parsed time values */
};
enum {ARG_ELIMIT=1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG};
struct arg_end
{
struct arg_hdr hdr; /* The mandatory argtable header struct */
int count; /* Number of errors encountered */
int *error; /* Array of error codes */
void **parent; /* Array of pointers to offending arg_xxx struct */
const char **argval; /* Array of pointers to offending argv[] string */
};
/**** arg_xxx constructor functions *********************************/
struct arg_rem* arg_rem(const char* datatype, const char* glossary);
struct arg_lit* arg_lit0(const char* shortopts,
const char* longopts,
const char* glossary);
struct arg_lit* arg_lit1(const char* shortopts,
const char* longopts,
const char *glossary);
struct arg_lit* arg_litn(const char* shortopts,
const char* longopts,
int mincount,
int maxcount,
const char *glossary);
struct arg_key* arg_key0(const char* keyword,
int flags,
const char* glossary);
struct arg_key* arg_key1(const char* keyword,
int flags,
const char* glossary);
struct arg_key* arg_keyn(const char* keyword,
int flags,
int mincount,
int maxcount,
const char* glossary);
struct arg_int* arg_int0(const char* shortopts,
const char* longopts,
const char* datatype,
const char* glossary);
struct arg_int* arg_int1(const char* shortopts,
const char* longopts,
const char* datatype,
const char *glossary);
struct arg_int* arg_intn(const char* shortopts,
const char* longopts,
const char *datatype,
int mincount,
int maxcount,
const char *glossary);
struct arg_dbl* arg_dbl0(const char* shortopts,
const char* longopts,
const char* datatype,
const char* glossary);
struct arg_dbl* arg_dbl1(const char* shortopts,
const char* longopts,
const char* datatype,
const char *glossary);
struct arg_dbl* arg_dbln(const char* shortopts,
const char* longopts,
const char *datatype,
int mincount,
int maxcount,
const char *glossary);
struct arg_str* arg_str0(const char* shortopts,
const char* longopts,
const char* datatype,
const char* glossary);
struct arg_str* arg_str1(const char* shortopts,
const char* longopts,
const char* datatype,
const char *glossary);
struct arg_str* arg_strn(const char* shortopts,
const char* longopts,
const char* datatype,
int mincount,
int maxcount,
const char *glossary);
struct arg_rex* arg_rex0(const char* shortopts,
const char* longopts,
const char* pattern,
const char* datatype,
int flags,
const char* glossary);
struct arg_rex* arg_rex1(const char* shortopts,
const char* longopts,
const char* pattern,
const char* datatype,
int flags,
const char *glossary);
struct arg_rex* arg_rexn(const char* shortopts,
const char* longopts,
const char* pattern,
const char* datatype,
int mincount,
int maxcount,
int flags,
const char *glossary);
struct arg_file* arg_file0(const char* shortopts,
const char* longopts,
const char* datatype,
const char* glossary);
struct arg_file* arg_file1(const char* shortopts,
const char* longopts,
const char* datatype,
const char *glossary);
struct arg_file* arg_filen(const char* shortopts,
const char* longopts,
const char* datatype,
int mincount,
int maxcount,
const char *glossary);
struct arg_date* arg_date0(const char* shortopts,
const char* longopts,
const char* format,
const char* datatype,
const char* glossary);
struct arg_date* arg_date1(const char* shortopts,
const char* longopts,
const char* format,
const char* datatype,
const char *glossary);
struct arg_date* arg_daten(const char* shortopts,
const char* longopts,
const char* format,
const char* datatype,
int mincount,
int maxcount,
const char *glossary);
struct arg_end* arg_end(int maxerrors);
/**** other functions *******************************************/
int arg_nullcheck(void **argtable);
int arg_parse(int argc, char **argv, void **argtable);
void arg_print_option(FILE *fp, const char *shortopts, const char *longopts, const char *datatype, const char *suffix);
void arg_print_syntax(FILE *fp, void **argtable, const char *suffix);
void arg_print_syntaxv(FILE *fp, void **argtable, const char *suffix);
void arg_print_glossary(FILE *fp, void **argtable, const char *format);
void arg_print_glossary_gnu(FILE *fp, void **argtable);
void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
void arg_freetable(void **argtable, size_t n);
void arg_print_formatted(FILE *fp, const unsigned lmargin, const unsigned rmargin, const char *text);
/**** deprecated functions, for back-compatibility only ********/
void arg_free(void **argtable);
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load Diff

@ -0,0 +1,42 @@
#include <stdio.h>
#include <console/console.h>
#include <console/utils.h>
#include <malloc.h>
#if CONSOLE_USE_MEMSTREAM
console_err_t console_filecap_init(console_filecap_t *cap) {
cap->buf = NULL;
cap->buf_size = 0;
cap->file = open_memstream(&cap->buf, &cap->buf_size);
if (!cap->file) {
return CONSOLE_ERR_NO_MEM;
}
return CONSOLE_OK;
}
void console_filecap_end(console_filecap_t *cap) {
// clean up
if (cap->file) {
fclose(cap->file);
cap->file = NULL;
}
if (cap->buf) {
free(cap->buf); // allocated by memstream
cap->buf = NULL;
}
}
void console_filecap_print_end(console_filecap_t *cap) {
fflush(cap->file);
fclose(cap->file);
cap->file = NULL;
if (cap->buf) {
console_write(cap->buf, cap->buf_size);
free(cap->buf); // allocated by memstream
cap->buf = NULL;
}
}
#endif

@ -0,0 +1,194 @@
// enable "vasprintf" from stdio.h
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "console/console.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
// These are either implemented using unix file descriptors, or in a platform specific way through a void* context
// - then the user code must provide the implementations.
#if CONSOLE_USE_FILE_IO_STREAMS
#include <unistd.h>
bool console_have_stdin_ctx(console_ctx_t *ctx)
{
return ctx && ctx->in &&
!feof(ctx->in);
}
/**
* Write to console context.
*
* This is also a Linenoise write callback.
*
* Return number of characters written, -1 on error.
*/
int __attribute__((weak)) console_write_ctx(console_ctx_t *ctx, const char *text, size_t len) {
if (!ctx || !ctx->out) {
return -1;
}
size_t written = fwrite(text, 1, len, ctx->out);
if (written != len) {
return -1;
}
return (int) written;
}
/**
* Read from console context's input stream.
*
* This is also a Linenoise read callback.
*
* Return number of characters read, -1 on error
*/
int __attribute__((weak)) console_read_ctx(console_ctx_t *ctx, char *dest, size_t count) {
if (!console_have_stdin_ctx(ctx)) return -1;
ssize_t readn = fread(dest, 1, (size_t) count, ctx->in);
return (int) readn;
}
#if CONSOLE_USE_TERMIOS
#include <termio.h>
int console_can_read_ctx(console_ctx_t *ctx) {
if (!console_have_stdin_ctx(ctx)) return -1;
int fd = fileno(ctx->in);
struct termios original;
tcgetattr(fd, &original);
struct termios term;
memcpy(&term, &original, sizeof(term));
term.c_lflag &= ~ICANON;
tcsetattr(fd, TCSANOW, &term);
int characters_buffered = 0;
ioctl(fd, FIONREAD, &characters_buffered);
tcsetattr(fd, TCSANOW, &original);
return characters_buffered;
}
#endif // CONSOLE_USE_TERMIOS
#endif // CONSOLE_USE_FILEDES_IO
ssize_t console_printf_ctx(console_ctx_t *ctx, console_color_t color, const char *format, ...) {
if (!ctx) return -1;
va_list list;
va_start(list, format);
ssize_t len = console_vprintf_ctx(ctx, color, format, list);
va_end(list);
return len;
}
ssize_t console_vprintf_ctx(console_ctx_t *ctx, console_color_t color, const char *format, va_list args) {
if (!ctx) return -1;
if (ctx->use_colors && color != COLOR_RESET) {
switch(color) {
case COLOR_BLACK:
console_write_ctx(ctx, "\x1b[30;1m", 7);
break;
case COLOR_RED:
console_write_ctx(ctx, "\x1b[31;1m", 7);
break;
case COLOR_GREEN:
console_write_ctx(ctx, "\x1b[32;1m", 7);
break;
case COLOR_YELLOW:
console_write_ctx(ctx, "\x1b[33;1m", 7);
break;
case COLOR_BLUE:
console_write_ctx(ctx, "\x1b[34;1m", 7);
break;
case COLOR_MAGENTA:
console_write_ctx(ctx, "\x1b[35;1m", 7);
break;
case COLOR_CYAN:
console_write_ctx(ctx, "\x1b[36;1m", 7);
break;
//case COLOR_WHITE:
default:
console_write_ctx(ctx, "\x1b[37;1m", 7);
break;
}
}
char *buf = NULL;
ssize_t len = vasprintf(&buf, format, args);
if (buf && len >= 0) {
// change to actual len written, can also result in -1 on error
len = console_write_ctx(ctx, buf, (size_t) len);
free(buf); // allocated by vasprintf
}
if (ctx->use_colors && color != COLOR_RESET) {
console_write_ctx(ctx, "\x1b[0m", 4);
len += 7+4;
}
return len;
}
int console_print_ctx(console_ctx_t *ctx, const char *text) {
if (!ctx) return -1;
return console_write_ctx(ctx, text, (int) strlen(text));
}
ssize_t console_println_ctx(console_ctx_t *ctx, const char *text) {
if (!ctx) return -1;
ssize_t n = console_write_ctx(ctx, text, (int) strlen(text));
if (n < 0) return n;
ssize_t m = console_write_ctx(ctx, "\n", 2);
if (m < 0) return m;
return n + m;
}
// ---------------- convenience functions -------------------
bool console_have_stdin(void) {
if (!console_context_available()) return false;
return console_have_stdin_ctx(console_active_ctx);
}
int console_can_read(void) {
if (!console_have_stdin()) return -1; // Input not available
return console_can_read_ctx(console_active_ctx);
}
/**
* Linenoise read callback.
*
* Return number of characters read, -1 on error
*/
ssize_t vconsole_read(char *dest, size_t count) {
if (!console_have_stdin()) return -1;
return console_read_ctx(console_active_ctx, dest, count);
}
/**
* Linenoise write callback.
*
* Return number of characters written, -1 on error.
*/
ssize_t vconsole_write(const char *text, size_t len) {
if (!console_context_available()) return -1;
return console_write_ctx(console_active_ctx, text, len);
}
ssize_t console_println(const char *text) {
if (!console_context_available()) return -1;
return console_println_ctx(console_active_ctx, text);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,248 @@
/* linenoise.h -- VERSION 1.0
*
* Guerrilla line editing library against the idea that a line editing lib
* needs to be 20,000 lines of C code.
*
* See linenoise.c for more information.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* THIS IS A MODIFIED VERSION THAT REMOVES GLOBAL STATE AND SUPPORTS
* OTHER IO THAN STDOUT/STDIN.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LINENOISE_H
#define LINENOISE_H
#ifndef LINENOISE_HISTORY_MAX_LINE
#define LINENOISE_HISTORY_MAX_LINE 512
#endif
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "console/config.h"
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
} linenoiseCompletions;
/**
* Get completions
*/
typedef void (linenoiseCompletionCallback)(const char *typed, linenoiseCompletions *);
/**
* Get a hint for typed text
*/
typedef char* (linenoiseHintsCallback)(const char *typed, int *color, int *bold);
/**
* Dispose of a hint returned by `linenoiseHintsCallback()`
*/
typedef void (linenoiseFreeHintsCallback)(void *);
/**
* Linenoise write callback.
*
* Return number of characters written, -1 on error.
* "ctx" may be a fd cast to void* or other value passed during LN init
*/
typedef int (linenoiseWrite)(void *ctx, const char *text, int len);
/**
* Linenoise read callback.
*
* Return number of characters read, -1 on error;
* "ctx" may be a fd cast to void* or other value passed during LN init
*/
typedef int (linenoiseRead)(void *ctx, char *dest, int count);
/**
* Linenoise state; exposed in header to allow static allocation
*
* Use `linenoiseStateInit()` to set default values.
*
* To shutdown, use `linenoiseHistoryFree()` and then free the struct as needed.
*/
struct linenoiseState {
bool mlmode; /* Multi line mode. Default is single line. */
bool dumbmode; /* Dumb mode where line editing is disabled. Off by default */
bool echomode; /* Echo (meaningful only in dumb mode) */
bool allowCtrlDExit;
int history_max_len;
int history_len;
char **history;
char *buf; /* Edited line buffer. */
size_t buflen; /* Edited line buffer size. */
const char *prompt; /* Prompt to display. */
size_t plen; /* Prompt length. */
int pos; /* Current cursor position. */
int oldpos; /* Previous refresh cursor position. */
int len; /* Current edited line length. */
int cols; /* Number of columns in terminal. */
int maxrows; /* Maximum num of rows used so far (multiline mode) */
int history_index; /* The history index we are currently editing. */
linenoiseCompletionCallback *completionCallback;
linenoiseHintsCallback *hintsCallback;
linenoiseFreeHintsCallback *freeHintsCallback;
void *rwctx;
linenoiseWrite *write;
linenoiseRead *read;
};
/**
* Initialize the state struct to defaults.
*
* Before the library is used, also set prompt, buffer,
* the read/write callbacks, r/w context (if used),
* completion, hint and free-hint callbacks
*/
void consLnStateInit(struct linenoiseState *ls);
/** Set buffer and its capacity */
static inline void consLnSetBuf(struct linenoiseState *ls, char *buf, size_t cap) {
ls->buf = buf;
ls->buflen = cap - 1; /* Make sure there is always space for the nulterm */
}
/** Set prompt pointer. Can be constant or dynamically allocated.
* Will NOT be mutated by the library. */
static inline void consLnSetPrompt(struct linenoiseState *ls, const char *prompt) {
ls->prompt = prompt;
}
/** Set buffer and its capacity */
static inline void consLnSetReadWrite(struct linenoiseState *ls, linenoiseRead *read, linenoiseWrite *write, void *ctx) {
ls->read = read;
ls->write = write;
ls->rwctx = ctx;
}
/** Set completion CB */
static inline void consLnSetCompletionCallback(struct linenoiseState *ls, linenoiseCompletionCallback *compl) {
ls->completionCallback = compl;
}
/** Set hint CB */
static inline void consLnSetHintsCallback(struct linenoiseState *ls, linenoiseHintsCallback *hints) {
ls->hintsCallback = hints;
}
/** Set free hints CB */
static inline void consLnSetFreeHintsCallback(struct linenoiseState *ls, linenoiseFreeHintsCallback *freeh) {
ls->freeHintsCallback = freeh;
}
/** This function is used by the callback function registered by the user
* in order to add completion options given the input string when the
* user typed <tab>. See the example.c source code for a very easy to
* understand example.
*
* The completion will be duplicated, it does not need to live past calling
* this function.
* */
void consLnAddCompletion(linenoiseCompletions *lc, const char *text);
/** The high level function that is the main API of the linenoise library. */
int consLnReadLine(struct linenoiseState *ls); // buffer is in "ls"
/** This is the API call to add a new entry in the linenoise history.
* It uses a fixed array of char pointers that are shifted (memmoved)
* when the history max length is reached in order to remove the older
* entry and make room for the new one, so it is not exactly suitable for huge
* histories, but will work well for a few hundred of entries.
*
* Using a circular buffer is smarter, but a bit more complex to handle. */
int consLnHistoryAdd(struct linenoiseState *ls, const char *line);
/** Set the maximum length for the history. This function can be called even
* if there is already some history, the function will make sure to retain
* just the latest 'len' elements if the new history length value is smaller
* than the amount of items already inside the history. */
int consLnHistorySetMaxLen(struct linenoiseState *ls, int len);
#if CONSOLE_FILE_SUPPORT
/** Save the history in the specified file. On success 0 is returned,
* on error -1 is returned. */
int consLnHistorySave(struct linenoiseState *ls, const char *filename);
/** Load the history from the specified file. If the file does not exist
* zero is returned and no operation is performed.
*
* If the file exists and the operation succeeded 0 is returned, otherwise
* on error -1 is returned. */
int consLnHistoryLoad(struct linenoiseState *ls, const char *filename);
#endif
/** Free history buffer for instance */
void consLnHistoryFree(struct linenoiseState *ls);
/** Clear the screen. Used to handle ctrl+l */
void consLnClearScreen(struct linenoiseState *ls);
/** Set if to use or not the multi line mode. */
static inline void consLnSetMultiLine(struct linenoiseState *ls, int ml) {
ls->mlmode = ml;
}
/** Get ml mode state */
static inline bool consLnGetMultiLine(struct linenoiseState *ls) {
return ls->mlmode;
}
/** Set if terminal does not recognize escape sequences */
static inline void consLnSetDumbMode(struct linenoiseState *ls, int dumb) {
ls->dumbmode = dumb;
}
/** Get dumb mode state */
static inline bool consLnGetDumbMode(struct linenoiseState *ls) {
return ls->dumbmode;
}
/** Enable/disable echo on keypress (only applies to dumb mode) */
static inline void consLnSetEchoMode(struct linenoiseState *ls, int set) {
ls->echomode = set;
}
/** Get echo mode state */
static inline bool consLnGetEchoMode(struct linenoiseState *ls) {
return ls->echomode;
}
#endif /* LINENOISE_H */

@ -0,0 +1,196 @@
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include "console/prefix_match.h"
int prefix_match(const char *value, const char **options, int flags) {
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // this doesn't make sense here
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE);
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
int (*cmpfn) (const char *, const char *) = case_sensitive ? strcmp : strcasecmp;
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp;
if (!value || !options) return -1;
size_t input_len = strlen(value);
const char *option = NULL;
int counter = 0;
int result = -1;
while (NULL != (option = options[counter])) {
if (cmpfn(option, value) == 0) {
return counter; // full exact match
} else {
// Test for partial match
if (can_abbrev && ncmpfn(value, option, input_len) == 0) {
if (result == -1) {
result = counter; // first partial match
} else {
// ambiguous match
return -1;
}
}
}
counter++;
}
return result;
}
size_t pm_word_len(const char * restrict word, const char * restrict delims) {
char d;
const char *dp = delims;
size_t word_len = 0;
if (!word || !delims) return 0;
while ('\0' != (d = *dp++)) {
char *end = strchr(word, d);
if (NULL == end) continue;
size_t len = end - word;
if (!word_len || len < word_len) {
word_len = len;
}
}
if (!word_len) {
word_len = strlen(word);
}
return word_len;
}
size_t pm_count_words(const char * restrict sentence, const char * restrict delims) {
char c;
size_t n = 0;
bool in_word = false;
if (!sentence || !delims) return 0;
while (0 != (c = *sentence++)) {
bool is_delim = NULL != strchr(delims, c);
if (is_delim && in_word) {
in_word = false;
} else if (!in_word && !is_delim) {
n++;
in_word = true;
}
}
return n;
}
const char *pm_skip_words(const char * restrict sentence, const char * restrict delims, size_t skip) {
char c;
size_t n = 0;
bool in_word = false;
if (!sentence || !delims) return NULL;
while (0 != (c = *sentence++)) {
bool is_delim = NULL != strchr(delims, c);
if (is_delim && in_word) {
in_word = false;
skip--;
if (skip == 0) {
return sentence - 1;
}
} else if (!in_word && !is_delim) {
n++;
in_word = true;
}
}
return sentence - 1;
}
enum pm_test_result prefix_multipart_test(
const char * restrict tested,
const char* restrict full,
const char * restrict delims,
int flags
) {
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE);
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp;
// lazy shortcut first...
if ((case_sensitive && 0 == strcmp(tested, full)) || (!case_sensitive && 0 == strcasecmp(tested, full))) {
return PM_TEST_MATCH; // full match
}
const char *word_t = tested;
const char *word_f = full;
size_t word_t_len = 0;
size_t word_f_len = 0;
while (1) {
word_t += word_t_len;
word_f += word_f_len;
// advance past leading delims, if any
while (*word_t != '\0' && NULL != strchr(delims, *word_t)) word_t++;
while (*word_f != '\0' && NULL != strchr(delims, *word_f)) word_f++;
// test for terminator
if (*word_t == '\0' && *word_f == '\0') {
// both ended at the same number of words
return PM_TEST_MATCH; // full match
}
if (*word_t == '\0' || *word_f == '\0') {
// sentences ended at different length
if (0 != (flags & PREFIXMATCH_MULTI_PARTIAL) && *word_f != '\0') { // word prefix match (a is a prefix of b)
return PM_TEST_MATCH_MULTI_PARTIAL;
} else {
return PM_TEST_NO_MATCH;
}
}
// find end of the words
word_t_len = pm_word_len(word_t, delims);
word_f_len = pm_word_len(word_f, delims);
if (word_t_len > word_f_len || (!can_abbrev && word_t_len != word_f_len)) {
return PM_TEST_NO_MATCH;
}
int cmp = ncmpfn(word_t, word_f, word_t_len);
if (0 != cmp) { // words differ
return PM_TEST_NO_MATCH;
}
}
}
int prefix_multipart_match(const char * restrict value, const char **options, const char * restrict delims, int flags) {
bool multi_partial = 0 != (flags & PREFIXMATCH_MULTI_PARTIAL);
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // turn it off for passing the to test fn
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
if (!value || !options) return -1;
const char *option = NULL;
int counter = 0;
int result = -1;
int result_partial = -1; // -1=none yet, -2=ambiguous multi-word partial, >=0=index
int result_partial_nwords = 0;
while (NULL != (option = options[counter])) {
if (PM_TEST_MATCH == prefix_multipart_test(value, option, delims, flags | PREFIXMATCH_NOABBREV)) {
return counter; // full exact match
} else if (can_abbrev) {
// Test for partial match
if (PM_TEST_MATCH == prefix_multipart_test(value, option, delims, flags)) {
if (result == -1) {
result = counter; // first partial match in all words
} else {
return -1;
}
} else if (multi_partial && PM_TEST_MATCH_MULTI_PARTIAL == prefix_multipart_test(value, option, delims, flags | PREFIXMATCH_MULTI_PARTIAL)) {
int nwords = pm_count_words(option, delims);
if (result_partial == -1 || result_partial_nwords < nwords) {
result_partial = counter; // first partial match
result_partial_nwords = nwords;
} else {
result_partial = -2;
}
}
}
counter++;
}
if (result != -1) {
return result;
}
if (result_partial >= 0) {
return result_partial;
}
return -1;
}

@ -0,0 +1,120 @@
// Copyright 2016-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define SS_FLAG_ESCAPE 0x8
typedef enum {
/* parsing the space between arguments */
SS_SPACE = 0x0,
/* parsing an argument which isn't quoted */
SS_ARG = 0x1,
/* parsing a quoted argument */
SS_QUOTED_ARG = 0x2,
/* parsing an escape sequence within unquoted argument */
SS_ARG_ESCAPED = SS_ARG | SS_FLAG_ESCAPE,
/* parsing an escape sequence within a quoted argument */
SS_QUOTED_ARG_ESCAPED = SS_QUOTED_ARG | SS_FLAG_ESCAPE,
} split_state_t;
size_t console_split_argv(char *line, char **argv, size_t argv_size)
{
const int QUOTE = '"';
const int ESCAPE = '\\';
const int SPACE = ' ';
split_state_t state = SS_SPACE;
int argc = 0;
char *next_arg_start = line;
char *out_ptr = line;
for (char *in_ptr = line; argc < (int)(argv_size - 1); ++in_ptr) {
int char_in = (unsigned char) *in_ptr;
if (char_in == 0) {
break;
}
int char_out = -1;
/* helper function, called when done with an argument */
void end_arg() {
char_out = 0;
argv[argc++] = next_arg_start;
state = SS_SPACE;
}
switch (state) {
case SS_SPACE:
if (char_in == SPACE) {
/* skip space */
} else if (char_in == QUOTE) {
next_arg_start = out_ptr;
state = SS_QUOTED_ARG;
} else if (char_in == ESCAPE) {
next_arg_start = out_ptr;
state = SS_ARG_ESCAPED;
} else {
next_arg_start = out_ptr;
state = SS_ARG;
char_out = char_in;
}
break;
case SS_QUOTED_ARG:
if (char_in == QUOTE) {
end_arg();
} else if (char_in == ESCAPE) {
state = SS_QUOTED_ARG_ESCAPED;
} else {
char_out = char_in;
}
break;
case SS_ARG_ESCAPED:
case SS_QUOTED_ARG_ESCAPED:
if (char_in == ESCAPE || char_in == QUOTE || char_in == SPACE) {
char_out = char_in;
} else {
/* unrecognized escape character, skip */
}
state = (split_state_t) (state & (~SS_FLAG_ESCAPE));
break;
case SS_ARG:
if (char_in == SPACE) {
end_arg();
} else if (char_in == ESCAPE) {
state = SS_ARG_ESCAPED;
} else {
char_out = char_in;
}
break;
}
/* need to output anything? */
if (char_out >= 0) {
*out_ptr = char_out;
++out_ptr;
}
}
/* make sure the final argument is terminated */
*out_ptr = 0;
/* finalize the last argument */
if (state != SS_SPACE && argc < ((int)argv_size - 1)) {
argv[argc++] = next_arg_start;
}
/* add a NULL at the end of argv */
argv[argc] = NULL;
return argc;
}

@ -0,0 +1,36 @@
/**
* Command argument splitting
*
* Created on 2020/02/28.
*/
#ifndef VCOM_CONSOLE_SPLIT_ARGV_H
#define VCOM_CONSOLE_SPLIT_ARGV_H
/**
* @brief Split command line into arguments in place
*
* - This function finds whitespace-separated arguments in the given input line.
*
* 'abc def 1 20 .3' -> [ 'abc', 'def', '1', '20', '.3' ]
*
* - Argument which include spaces may be surrounded with quotes. In this case
* spaces are preserved and quotes are stripped.
*
* 'abc "123 456" def' -> [ 'abc', '123 456', 'def' ]
*
* - Escape sequences may be used to produce backslash, double quote, and space:
*
* 'a\ b\\c\"' -> [ 'a b\c"' ]
*
* Pointers to at most argv_size - 1 arguments are returned in argv array.
* The pointer after the last one (i.e. argv[argc]) is set to NULL.
*
* @param line pointer to buffer to parse; it is modified in place
* @param argv array where the pointers to arguments are written
* @param argv_size number of elements in argv_array (max. number of arguments)
* @return number of arguments found (argc)
*/
size_t console_split_argv(char *line, char **argv, size_t argv_size);
#endif //VCOM_CONSOLE_SPLIT_ARGV_H

@ -0,0 +1,204 @@
//
// Created by MightyPork on 2020/03/11.
//
#include <string.h>
#include <stdint.h>
#include "console/console.h"
#include "console/utils.h"
#if (CONSOLE_HAVE_CSP && !CSP_POSIX) || CONSOLE_TESTING_ALLOC_FUNCS
#include <csp/arch/csp_malloc.h>
#else
#include <malloc.h>
#endif
// Based on: https://stackoverflow.com/a/7776146/2180189
void console_hexdump(const void *data, size_t len)
{
size_t i;
char asciibuf[17];
const unsigned char *pc = (const unsigned char *) data;
// Length checks.
if (len <= 0) {
console_print("BAD LENGTH\r\n");
return;
}
// Process every byte in the data.
for (i = 0; i < len; i++) {
size_t ai = i % 16;
// Multiple of 16 means new line (with line offset).
if (ai == 0) {
// Don't print ASCII buffer for the "zeroth" line.
if (i != 0) {
console_printf(" |%s|\r\n", asciibuf);
}
// Output the offset.
console_printf("%4d :", (int)i);
}
// Now the hex code for the specific character.
console_printf(" %02x", pc[i]);
// And buffer a printable ASCII character for later.
if ((pc[i] < 0x20) || (pc[i] > 0x7e)) {
asciibuf[ai] = '.';
}
else {
asciibuf[ai] = (char) pc[i];
}
asciibuf[ai + 1] = '\0';
}
// Pad out last line if not exactly 16 characters.
while ((i % 16) != 0) {
console_print(" ");
i++;
}
// And print the final ASCII buffer.
console_printf(" |%s|\r\n", asciibuf);
}
static int hex_char_decode(char x)
{
if (x >= '0' && x <= '9') {
return x - '0';
}
else if (x >= 'a' && x <= 'f') {
return 10 + (x - 'a');
}
else if (x >= 'A' && x <= 'F') {
return 10 + (x - 'A');
}
else {
return -1;
}
}
int console_base16_decode(const char *hex, void *dest, size_t capacity)
{
if (!hex || !dest) return -1;
const char *px = (const char *) hex;
uint8_t *pd = (unsigned char *) dest;
int hlen = (int) strlen(hex);
if (hlen % 2) {
return -2;
}
int blen = hlen / 2;
if (blen > (int) capacity) {
return -3;
}
int v;
uint8_t byte;
for (int i = 0; i < blen; i++) {
v = hex_char_decode(*px++);
if (v == -1) {
return -2;
}
byte = (v & 0x0F) << 4;
v = hex_char_decode(*px++);
if (v == -1) {
return -2;
}
byte |= (v & 0x0F);
*pd++ = byte;
}
return blen;
}
void * __attribute__((malloc)) console_malloc(size_t size) {
#if (CONSOLE_HAVE_CSP && !CSP_POSIX) || CONSOLE_TESTING_ALLOC_FUNCS
return csp_malloc(size);
#else
return malloc(size);
#endif
}
void * console_realloc(void *ptr, size_t oldsize, size_t newsize) {
#if (CONSOLE_HAVE_CSP && !CSP_POSIX) || CONSOLE_TESTING_ALLOC_FUNCS
// csp / freertos do not provide realloc, simply allocate a new one and copy data over.
if (oldsize >= newsize) {
// no realloc needed
if (!ptr) { // NULL was given, act as malloc
ptr = csp_malloc(newsize);
}
return ptr;
}
void *tmp = csp_malloc(newsize);
if (!tmp) {
// "If realloc() fails, the original block is left untouched; it is not freed or moved."
return NULL;
}
if (ptr) {
// copy if there is anything to copy from
memcpy(tmp, ptr, oldsize); // we know oldsize < newsize, otherwise the check above returns.
// discard the old buffer
csp_free(ptr);
}
return tmp;
#else
(void) oldsize; // unused
return realloc(ptr, newsize);
#endif
}
void * __attribute__((malloc,alloc_size(1,2))) console_calloc(size_t nmemb, size_t size) {
#if (CONSOLE_HAVE_CSP && !CSP_POSIX) || CONSOLE_TESTING_ALLOC_FUNCS
return csp_calloc(nmemb, size);
#else
return calloc(nmemb, size);
#endif
}
void console_free(void *ptr) {
#if (CONSOLE_HAVE_CSP && !CSP_POSIX) || CONSOLE_TESTING_ALLOC_FUNCS
return csp_free(ptr);
#else
return free(ptr);
#endif
}
char * console_strdup(const char *ptr) {
#if (CONSOLE_HAVE_CSP && !CSP_POSIX) || CONSOLE_TESTING_ALLOC_FUNCS
if (!ptr) return NULL;
size_t len = strlen(ptr);
char *copy = console_malloc(len+1);
if (!copy) return NULL;
strncpy(copy, ptr, len);
copy[len]=0;
return copy;
#else
return strdup(ptr);
#endif
}
char * console_strndup(const char *ptr, size_t maxlen) {
#if (CONSOLE_HAVE_CSP && !CSP_POSIX) || CONSOLE_TESTING_ALLOC_FUNCS
if (!ptr) return NULL;
size_t len = strnlen(ptr, maxlen);
char *copy = console_malloc(len+1);
if (!copy) return NULL;
strncpy(copy, ptr, len);
copy[len]=0;
return copy;
#else
return strndup(ptr, maxlen);
#endif
}

@ -0,0 +1,645 @@
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* $FreeBSD$
*/
#ifndef _SYS_QUEUE_H_
#define _SYS_QUEUE_H_
#include <sys/cdefs.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A singly-linked tail queue is headed by a pair of pointers, one to the
* head of the list and the other to the tail of the list. The elements are
* singly linked for minimum space and pointer manipulation overhead at the
* expense of O(n) removal for arbitrary elements. New elements can be added
* to the list after an existing element, at the head of the list, or at the
* end of the list. Elements being removed from the head of the tail queue
* should use the explicit macro for this purpose for optimum efficiency.
* A singly-linked tail queue may only be traversed in the forward direction.
* Singly-linked tail queues are ideal for applications with large datasets
* and few or no removals or for implementing a FIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may only be traversed in the forward direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* For details on the use of these macros, see the queue(3) manual page.
*
*
* SLIST LIST STAILQ TAILQ
* _HEAD + + + +
* _HEAD_INITIALIZER + + + +
* _ENTRY + + + +
* _INIT + + + +
* _EMPTY + + + +
* _FIRST + + + +
* _NEXT + + + +
* _PREV - - - +
* _LAST - - + +
* _FOREACH + + + +
* _FOREACH_SAFE + + + +
* _FOREACH_REVERSE - - - +
* _FOREACH_REVERSE_SAFE - - - +
* _INSERT_HEAD + + + +
* _INSERT_BEFORE - + - +
* _INSERT_AFTER + + + +
* _INSERT_TAIL - - + +
* _CONCAT - - + +
* _REMOVE_AFTER + - + -
* _REMOVE_HEAD + - + -
* _REMOVE + + + +
*
*/
#ifdef QUEUE_MACRO_DEBUG
/* Store the last 2 places the queue element or head was altered */
struct qm_trace {
char * lastfile;
int lastline;
char * prevfile;
int prevline;
};
#define TRACEBUF struct qm_trace trace;
#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
#define QMD_TRACE_HEAD(head) do { \
(head)->trace.prevline = (head)->trace.lastline; \
(head)->trace.prevfile = (head)->trace.lastfile; \
(head)->trace.lastline = __LINE__; \
(head)->trace.lastfile = __FILE__; \
} while (0)
#define QMD_TRACE_ELEM(elem) do { \
(elem)->trace.prevline = (elem)->trace.lastline; \
(elem)->trace.prevfile = (elem)->trace.lastfile; \
(elem)->trace.lastline = __LINE__; \
(elem)->trace.lastfile = __FILE__; \
} while (0)
#else
#define QMD_TRACE_ELEM(elem)
#define QMD_TRACE_HEAD(head)
#define QMD_SAVELINK(name, link)
#define TRACEBUF
#define TRASHIT(x)
#endif /* QUEUE_MACRO_DEBUG */
/*
* Singly-linked List declarations.
*/
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
/*
* Singly-linked List functions.
*/
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_FOREACH(var, head, field) \
for ((var) = SLIST_FIRST((head)); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = SLIST_FIRST((head)); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
for ((varp) = &SLIST_FIRST((head)); \
((var) = *(varp)) != NULL; \
(varp) = &SLIST_NEXT((var), field))
#define SLIST_INIT(head) do { \
SLIST_FIRST((head)) = NULL; \
} while (0)
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
if (SLIST_FIRST((head)) == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = SLIST_FIRST((head)); \
while (SLIST_NEXT(curelm, field) != (elm)) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_REMOVE_AFTER(curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define SLIST_REMOVE_AFTER(elm, field) do { \
SLIST_NEXT(elm, field) = \
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
} while (0)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type) \
struct { \
struct type *stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_CONCAT(head1, head2) do { \
if (!STAILQ_EMPTY((head2))) { \
*(head1)->stqh_last = (head2)->stqh_first; \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_INIT((head2)); \
} \
} while (0)
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY((head)) ? \
NULL : \
((struct type *)(void *) \
((char *)((head)->stqh_last) - __offsetof(struct type, field))))
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = STAILQ_FIRST((head)); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
STAILQ_REMOVE_AFTER(head, curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_SWAP(head1, head2, type) do { \
struct type *swap_first = STAILQ_FIRST(head1); \
struct type **swap_last = (head1)->stqh_last; \
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_FIRST(head2) = swap_first; \
(head2)->stqh_last = swap_last; \
if (STAILQ_EMPTY(head1)) \
(head1)->stqh_last = &STAILQ_FIRST(head1); \
if (STAILQ_EMPTY(head2)) \
(head2)->stqh_last = &STAILQ_FIRST(head2); \
} while (0)
#define STAILQ_INSERT_CHAIN_HEAD(head, elm_chead, elm_ctail, field) do { \
if ((STAILQ_NEXT(elm_ctail, field) = STAILQ_FIRST(head)) == NULL ) { \
(head)->stqh_last = &STAILQ_NEXT(elm_ctail, field); \
} \
STAILQ_FIRST(head) = (elm_chead); \
} while (0)
/*
* List declarations.
*/
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
/*
* List functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
#define QMD_LIST_CHECK_HEAD(head, field) do { \
if (LIST_FIRST((head)) != NULL && \
LIST_FIRST((head))->field.le_prev != \
&LIST_FIRST((head))) \
panic("Bad list head %p first->prev != head", (head)); \
} while (0)
#define QMD_LIST_CHECK_NEXT(elm, field) do { \
if (LIST_NEXT((elm), field) != NULL && \
LIST_NEXT((elm), field)->field.le_prev != \
&((elm)->field.le_next)) \
panic("Bad link elm %p next->prev != elm", (elm)); \
} while (0)
#define QMD_LIST_CHECK_PREV(elm, field) do { \
if (*(elm)->field.le_prev != (elm)) \
panic("Bad link elm %p prev->next != elm", (elm)); \
} while (0)
#else
#define QMD_LIST_CHECK_HEAD(head, field)
#define QMD_LIST_CHECK_NEXT(elm, field)
#define QMD_LIST_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
QMD_LIST_CHECK_NEXT(listelm, field); \
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
LIST_NEXT((listelm), field)->field.le_prev = \
&LIST_NEXT((elm), field); \
LIST_NEXT((listelm), field) = (elm); \
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
QMD_LIST_CHECK_PREV(listelm, field); \
(elm)->field.le_prev = (listelm)->field.le_prev; \
LIST_NEXT((elm), field) = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
} while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \
QMD_LIST_CHECK_HEAD((head), field); \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_REMOVE(elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.le_next); \
QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
QMD_LIST_CHECK_NEXT(elm, field); \
QMD_LIST_CHECK_PREV(elm, field); \
if (LIST_NEXT((elm), field) != NULL) \
LIST_NEXT((elm), field)->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
} while (0)
#define LIST_SWAP(head1, head2, type, field) do { \
struct type *swap_tmp = LIST_FIRST((head1)); \
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
LIST_FIRST((head2)) = swap_tmp; \
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
} while (0)
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
TRACEBUF \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
TRACEBUF \
}
/*
* Tail queue functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
if (!TAILQ_EMPTY(head) && \
TAILQ_FIRST((head))->field.tqe_prev != \
&TAILQ_FIRST((head))) \
panic("Bad tailq head %p first->prev != head", (head)); \
} while (0)
#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
if (*(head)->tqh_last != NULL) \
panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
} while (0)
#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
if (TAILQ_NEXT((elm), field) != NULL && \
TAILQ_NEXT((elm), field)->field.tqe_prev != \
&((elm)->field.tqe_next)) \
panic("Bad link elm %p next->prev != elm", (elm)); \
} while (0)
#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
if (*(elm)->field.tqe_prev != (elm)) \
panic("Bad link elm %p prev->next != elm", (elm)); \
} while (0)
#else
#define QMD_TAILQ_CHECK_HEAD(head, field)
#define QMD_TAILQ_CHECK_TAIL(head, headname)
#define QMD_TAILQ_CHECK_NEXT(elm, field)
#define QMD_TAILQ_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
QMD_TRACE_HEAD(head1); \
QMD_TRACE_HEAD(head2); \
} \
} while (0)
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
for ((var) = TAILQ_LAST((head), headname); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
QMD_TAILQ_CHECK_NEXT(listelm, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else { \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
} \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&listelm->field); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
QMD_TAILQ_CHECK_PREV(listelm, field); \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&listelm->field); \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
QMD_TAILQ_CHECK_HEAD(head, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
QMD_TAILQ_CHECK_TAIL(head, field); \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_REMOVE(head, elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
QMD_TAILQ_CHECK_NEXT(elm, field); \
QMD_TAILQ_CHECK_PREV(elm, field); \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else { \
(head)->tqh_last = (elm)->field.tqe_prev; \
QMD_TRACE_HEAD(head); \
} \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_SWAP(head1, head2, type, field) do { \
struct type *swap_first = (head1)->tqh_first; \
struct type **swap_last = (head1)->tqh_last; \
(head1)->tqh_first = (head2)->tqh_first; \
(head1)->tqh_last = (head2)->tqh_last; \
(head2)->tqh_first = swap_first; \
(head2)->tqh_last = swap_last; \
if ((swap_first = (head1)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head1)->tqh_first; \
else \
(head1)->tqh_last = &(head1)->tqh_first; \
if ((swap_first = (head2)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head2)->tqh_first; \
else \
(head2)->tqh_last = &(head2)->tqh_first; \
} while (0)
#ifdef __cplusplus
}
#endif
#endif /* !_SYS_QUEUE_H_ */

@ -0,0 +1,48 @@
idf_component_register(SRCS
user_main.c
settings.c
shutdown_handlers.c
sntp_cli.c
utils.c
wifi_conn.c
mbiface.c
actuators.c
console/console_ioimpl.c
console/console_server.c
console/register_cmds.c
console/telnet_parser.c
console/commands/cmd_dump.c
console/commands/cmd_factory_reset.c
console/commands/cmd_heap.c
console/commands/cmd_ip.c
console/commands/cmd_restart.c
console/commands/cmd_tasks.c
console/commands/cmd_version.c
console/commands/cmd_wifi.c
console/commands/cmd_pw.c
console/commands/cmd_pwm.c
INCLUDE_DIRS ".")
find_package(Git REQUIRED)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE _commit_hash
)
# TODO what's this?
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE _revision_number
)
string(REGEX REPLACE "\n" "" _commit_hash "${_commit_hash}")
string(REGEX REPLACE "\n" "" _revision_number "${_revision_number}")
string(TIMESTAMP _build_time_stamp)
configure_file(
"gitversion.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/config/gitversion.h"
)
include_directories("${CMAKE_CURRENT_BINARY_DIR}/config")

@ -0,0 +1,27 @@
menu "Project configuration"
menu "Pin mapping"
config PIN_STATUSLED
int "Status LED pin"
default 5
config PIN_PWM
int "Motor PWM pin"
default 16
config PIN_INT
int "Counter input"
default 21
endmenu
menu "Console"
config CONSOLE_TELNET_PORT
int "Integrated telnet server listening port"
default 23
config CONSOLE_PW_LEN
int "Console max pw len"
default 32
endmenu
endmenu

@ -0,0 +1,194 @@
//
// Created by MightyPork on 2022/08/20.
//
#include <driver/ledc.h>
#include <esp_log.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "freertos/queue.h"
#include "actuators.h"
#include "tasks.h"
#include "settings.h"
static const char *TAG="act";
// TODO move these to settings and make them accessible via modbus
uint32_t pwm_freq = 3000;
uint32_t pwm_duty = 600;
uint32_t pwm_thres = 850;
uint32_t pwm_on = 1;
uint32_t last_adc_mv = 0;
uint32_t tick_count = 0;
uint32_t tick_count_start = 0;
uint32_t last_cpm = 0;
uint32_t count_total = 0;
float last_usv_h = 0.0f;
float total_usv = 0.0f;
static xQueueHandle gpio_evt_queue = NULL;
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
static void TickHandlingTask(void* arg)
{
uint32_t io_num;
for(;;) {
if(xQueueReceive(gpio_evt_queue, &io_num, pdMS_TO_TICKS(20))) {
printf("TICK! ");
act_statusled_set(0);
tick_count++;
count_total++;
} else {
act_statusled_set(1);
}
if (tick_count_start == 0) {
tick_count_start = xTaskGetTickCount();
} else {
if (xTaskGetTickCount() - tick_count_start >= pdMS_TO_TICKS(60000)) {
last_cpm = tick_count;
tick_count = 0;
tick_count_start = xTaskGetTickCount();
// https://sites.google.com/site/diygeigercounter/technical/gm-tubes-supported
last_usv_h = (float)last_cpm / 153.8f;
total_usv += (last_usv_h / 60.0f);
printf("\r\n\r\nCPM = %d ~~ %f uSv/h\r\n\r\n", last_cpm, last_usv_h);
}
}
}
}
void power_task()
{
while (1) {
const uint32_t val = act_read_adc();
act_pwm_set(val <= pwm_thres);
last_adc_mv = val;
// basically, just yield
vTaskDelay(pdMS_TO_TICKS(1));
}
}
void act_pwm_update_conf()
{
ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_TIMER_10_BIT,
.freq_hz = pwm_freq,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.timer_num = LEDC_TIMER_1,
.clk_cfg = LEDC_AUTO_CLK,
};
ledc_timer_config(&ledc_timer);
}
static void pwm_init()
{
act_pwm_update_conf();
// PWM output
ledc_channel_config_t chan = {
.channel = LEDC_CHANNEL_1,
.duty = 512,
.gpio_num = CONFIG_PIN_PWM,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.hpoint = 0,
.timer_sel = LEDC_TIMER_1,
.flags.output_invert = false,
};
ledc_channel_config(&chan);
ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 0);
ledc_stop(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 0);
}
void act_pwm_set(bool on)
{
if (on) {
pwm_on = 1;
ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, pwm_duty);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1);
} else {
pwm_on = 0;
ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 0);
ledc_stop(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 0);
}
}
static void led_init()
{
gpio_config_t ioconf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1 << CONFIG_PIN_STATUSLED),
};
gpio_config(&ioconf);
}
#define DEFAULT_VREF 1100
#define CHANNEL ADC1_CHANNEL_6 /* 34 */
#define WIDTH ADC_WIDTH_BIT_12
#define ATTEN ADC_ATTEN_DB_0
#define ADCX ADC_UNIT_1
static esp_adc_cal_characteristics_t adc_chars;
static void adc_init() {
adc1_config_width(WIDTH);
adc1_config_channel_atten(CHANNEL, ATTEN);
esp_adc_cal_characterize(ADCX, ATTEN, WIDTH, DEFAULT_VREF, &adc_chars);
// route vref to GPIO25 so it can be measured
esp_err_t status = adc_vref_to_gpio(ADC_UNIT_2, GPIO_NUM_25);
if (status == ESP_OK) {
printf("v_ref routed to GPIO\n");
} else {
printf("failed to route v_ref\n");
}
}
uint32_t act_read_adc()
{
uint32_t raw = adc1_get_raw(CHANNEL);
return esp_adc_cal_raw_to_voltage(raw, &adc_chars);
}
void act_statusled_set(bool on)
{
gpio_set_level(CONFIG_PIN_STATUSLED, (int) on);
}
static void int_init() {
gpio_pad_select_gpio(CONFIG_PIN_INT);
gpio_set_direction(CONFIG_PIN_INT, GPIO_MODE_INPUT);
gpio_pulldown_en(CONFIG_PIN_INT);
gpio_pullup_dis(CONFIG_PIN_INT);
gpio_set_intr_type(CONFIG_PIN_INT, GPIO_INTR_POSEDGE);
gpio_evt_queue = xQueueCreate(20, sizeof(int));
xTaskCreate(TickHandlingTask, "ticks", 3000, NULL, PRIO_NORMAL, NULL);
gpio_isr_handler_add(CONFIG_PIN_INT, gpio_isr_handler, (void *)CONFIG_PIN_INT);
}
void act_init()
{
pwm_freq = gSettings.pwm_freq;
pwm_duty = gSettings.pwm_duty;
pwm_thres = gSettings.pwm_thres;
led_init();
pwm_init();
adc_init();
act_statusled_set(1); // = off
xTaskCreate(power_task, "pwr", 3000, NULL, PRIO_HIGH, NULL);
int_init();
}

@ -0,0 +1,29 @@
//
// Created by MightyPork on 2022/08/20.
//
#ifndef FANCTL_ACTUATORS_H
#define FANCTL_ACTUATORS_H
#include <stdbool.h>
#include <stdint.h>
void act_init();
extern uint32_t pwm_freq;
extern uint32_t pwm_duty;
extern uint32_t pwm_on;
extern uint32_t pwm_thres;
extern uint32_t last_adc_mv;
extern uint32_t last_cpm;
extern uint32_t count_total;
extern float last_usv_h;
extern float total_usv;
void act_pwm_set(bool on);
void act_pwm_update_conf();
void act_statusled_set(bool on);
uint32_t act_read_adc();
#endif //FANCTL_ACTUATORS_H

@ -0,0 +1,26 @@
/**
* Globals
*/
#ifndef _APPLICATION_H
#define _APPLICATION_H
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include "sdkconfig.h"
#include "settings.h"
#include "gitversion.h"
#define EG_WIFI_CONNECTED_BIT BIT0
extern EventGroupHandle_t g_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
esp_err_t cspemu_add_shutdown_handler(shutdown_handler_t handler);
void cspemu_run_shutdown_handlers(void);
#define APP_NAME "RECUP"
#define APP_VERSION "v1"
#endif //_APPLICATION_H

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

@ -0,0 +1,48 @@
//
// Created by MightyPork on 2018/12/02.
//
#ifndef CSPEMU_CMD_COMMON_H
#define CSPEMU_CMD_COMMON_H
#include "console_server.h"
//// prototypes (TODO remove..)
//void csp_vprintf(const char *format, va_list args);
//void csp_printf(const char *format, ...);
#define EOL "\r\n"
#define console_printf_e(format, ...) console_printf("\x1b[31m" format "\x1b[m", ##__VA_ARGS__)
#define console_printf_w(format, ...) console_printf("\x1b[33m" format "\x1b[m", ##__VA_ARGS__)
#define console_fputs(str) console_print(str)
#define console_fputsn(str, len) console_write(str, len)
#define console_fputc(c) console_write(&c, 1)
#define MSG_ON "\x1b[32mON\x1b[m"
#define MSG_ENABLED "\x1b[32mENABLED\x1b[m"
#define MSG_OFF "\x1b[31mOFF\x1b[m"
#define MSG_DISABLED "\x1b[31mDISABLED\x1b[m"
//#define ARG_OPTIONAL_NODE_ID() cmd_args.node->count ? cmd_args.node->ival[0] : csp_get_address()
#if 0
struct cmd_no_args_s {
struct arg_end *end;
};
extern struct cmd_no_args_s cmd_no_args;
#define CMD_CHECK_ARGS(struct_var) do { \
int nerrors = arg_parse(argc, argv, (void**) &struct_var); \
if (nerrors != 0) { \
console_print_errors(struct_var.end, argv[0]); \
return 1; \
} \
} while(0)
/** Report & return error if any args were given to this command */
#define CMD_CHECK_NO_ARGS() CMD_CHECK_ARGS(cmd_no_args)
#endif
#endif //CSPEMU_CMD_COMMON_H

@ -0,0 +1,34 @@
//
// Created by MightyPork on 2018/12/08.
//
#include "settings.h"
#include "console/cmd_common.h"
#include <console/cmddef.h>
static int cmd_dump(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_end *end;
} cmd_args;
if (reg) {
cmd_args.end = arg_end(1);
reg->argtable = &cmd_args;
reg->command = "dump";
reg->help = "Dump node info";
return 0;
}
console_printf("WiFi enabled: %d\r\n", gSettings.wifi_enabled);
// TODO show more settings
return 0;
}
void register_cmd_dump(void)
{
console_cmd_register(cmd_dump, "dump");
}

@ -0,0 +1,50 @@
//
// Created by MightyPork on 2018/12/08.
//
#include <string.h>
#include <linenoise/linenoise.h>
#include <settings.h>
#include <utils.h>
#include <nvs_flash.h>
#include "console/cmd_common.h"
#include <console/cmddef.h>
static int cmd_factory_reset(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_str *magic;
struct arg_end *end;
} cmd_args;
if (reg) {
cmd_args.magic = arg_str1(NULL, NULL, "<passphrase>", "Passphrase to prevent accidental erase. Must be 'confirm'");
cmd_args.end = arg_end(2);
reg->argtable = &cmd_args;
reg->command = "factory_reset";
reg->help = "Wipe non-volatile data memory, restoring everything to defaults.";
return 0;
}
if (streq(cmd_args.magic->sval[0], "confirm")) {
nvs_flash_erase();
console_printf("Non-volatile memory erased.\r\n"
"Restarting to apply changes...\r\n\r\n");
vTaskDelay(pdMS_TO_TICKS(500));
esp_restart();
} else {
console_printf("Incorrect passphrase.\r\n");
return 1;
}
return 0;
}
void register_cmd_factory_reset(void)
{
console_cmd_register(cmd_factory_reset, "factory_reset");
}

@ -0,0 +1,38 @@
//
// Created by MightyPork on 2018/12/08.
//
#include <esp_heap_caps.h>
#include <esp_system.h>
#include "console/cmd_common.h"
#include <console/cmddef.h>
/* 'heap' command prints minumum heap size */
static int cmd_heap(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_end *end;
} cmd_args;
if (reg) {
cmd_args.end = arg_end(1);
reg->argtable = &cmd_args;
reg->command = "heap";
reg->help = "Get emulator heap usage stats (for debug)";
return 0;
}
uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
uint32_t current_free = esp_get_free_heap_size();
console_printf("free heap: %u bytes\r\n"
" lowest: %u bytes\r\n", current_free, heap_size);
return 0;
}
void register_cmd_heap(void)
{
console_cmd_register(cmd_heap, "heap");
}

@ -0,0 +1,341 @@
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <esp_wifi.h>
#include <settings.h>
#include <lwip/inet.h>
#include "console/cmd_common.h"
#include <console/cmddef.h>
#include <application.h>
#include <common_utils/utils.h>
#include <sntp_cli.h>
#include <ping.h>
#include <lwip/netdb.h>
static int cmd_ip_status(console_ctx_t *ctx, cmd_signature_t *reg)
{
EMPTY_CMD_SETUP("Show IP status");
if (!gSettings.wifi_enabled || !g_State.wifi_inited) {
console_color_printf(COLOR_RED, "WiFi interface is disabled\n");
return 0;
}
wifi_config_t config;
if (ESP_OK == esp_wifi_get_config(ESP_IF_WIFI_STA, &config)) {
if (config.sta.ssid[0]) {
EventBits_t bits = xEventGroupGetBits(g_wifi_event_group);
if (bits & WIFI_CONNECTED_BIT) {
console_color_printf(COLOR_GREEN, "Connected to SSID \"%s\"\n", config.sta.ssid);
} else if (bits & WIFI_FAIL_BIT) {
console_color_printf(COLOR_RED, "WiFi connection failed.\n");
console_printf("Saved SSID = \"%s\"\n", config.sta.ssid);
return 0;
} else {
console_color_printf(COLOR_RED, "Not connected, retries may be in progress.\n");
console_printf("Saved SSID = \"%s\"\n", config.sta.ssid);
return 0;
}
tcpip_adapter_ip_info_t ipinfo;
if (ESP_OK == tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipinfo)) {
// ! inet_ntoa uses a global static buffer, cant use multiple in one printf call
console_printf("DHCP: %s\n", gSettings.dhcp_enable ? MSG_ENABLED : MSG_DISABLED " (static IP)");
console_printf("IP: %s\n", inet_ntoa(ipinfo.ip.addr));
console_printf("Mask: %s\n", inet_ntoa(ipinfo.netmask.addr));
console_printf("Gateway: %s\n", inet_ntoa(ipinfo.gw.addr));
if (!gSettings.dhcp_enable) {
console_printf("DNS: %s\n", inet_ntoa(gSettings.static_dns));
}
uint8_t mac[6];
if (ESP_OK == esp_wifi_get_mac(ESP_IF_WIFI_STA, mac)) {
console_printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
} else {
console_color_printf(COLOR_RED, "No IP address assigned!\n");
}
}
else {
console_color_printf(COLOR_RED, "SSID not configured!\n");
}
}
return 0;
}
static int cmd_ip_pingwd(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_str *cmd;
struct arg_end *end;
} cmd_args;
if (reg) {
cmd_args.cmd = arg_str0(NULL, NULL, "<cmd>", "Enable or disable. Omit to check current state");
cmd_args.end = arg_end(1);
reg->argtable = &cmd_args;
reg->help = "Enable/disable ping watchdog, or check the current setting. The watchdog periodically pings the gateway and restarts WiFi on failure.";
return 0;
}
if (cmd_args.cmd->count) {
int b = parse_boolean_arg(cmd_args.cmd->sval[0]);
if (b < 0) return CONSOLE_ERR_INVALID_ARG;
gSettings.dhcp_wd_enable = b;
settings_persist(SETTINGS_dhcp_wd_enable);
}
console_printf("Ping WD = %s\n", gSettings.dhcp_wd_enable ? MSG_ENABLED : MSG_DISABLED);
if (cmd_args.cmd->count) {
console_printf("Restart to apply changes\n");
}
return 0;
}
static void ping_success_print_cb(int bytes, const char *ip, int seq, int elapsed_ms) {
console_printf("Rx %d bytes from %s: icmp_seq=%d time=%d ms\n", bytes, ip, seq, (int) elapsed_ms);
}
static void ping_fail_print_cb(int seq) {
console_printf("Request timeout for icmp_seq %d\n", seq);
}
static int cmd_ip_ping(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_str *ip;
struct arg_int *num;
struct arg_int *timeout;
struct arg_int *interval;
struct arg_int *size;
struct arg_end *end;
} args;
if (reg) {
args.ip = arg_str1(NULL, NULL, "<IP>", "Target IP");
args.num = arg_int0("n", NULL, "<num>", "Ping count (def 1)");
args.timeout = arg_int0("t", NULL, "<ms>", "Timeout (def 3000)");
args.interval = arg_int0("i", NULL, "<ms>", "Interval (def 1000)");
args.size = arg_int0("s", NULL, "<bytes>", "Payload size (def 32)");
args.end = arg_end(5);
reg->argtable = &args;
reg->help = "Ping a host using ICMP";
return 0;
}
ping_opts_t opts = PING_CONFIG_DEFAULT();
opts.success_cb = ping_success_print_cb;
opts.fail_cb = ping_fail_print_cb;
opts.count = args.num->count ? args.num->ival[0] : 1;
opts.payload_size = args.size->count ? args.size->ival[0] : 32;
opts.interval_ms = args.interval->count ? args.interval->ival[0] : 1000;
opts.timeout_ms = args.timeout->count ? args.timeout->ival[0] : 3000;
if (0 == inet_aton(args.ip->sval[0], &opts.ip_addr)) {
// we could have received a domain name here
struct hostent * ent = gethostbyname(args.ip->sval[0]);
if (!ent) {
console_println("Could not resolve");
return CONSOLE_ERR_IO;
}
memcpy(&opts.ip_addr, ent->h_addr_list[0], sizeof(ip4_addr_t));
console_printf("Resolved as %s\n", inet_ntoa(opts.ip_addr));
}
ping_result_t result = {};
esp_err_t ret = ping(&opts, &result);
if (ret != ESP_OK) {
console_println("Ping error");
return ret;
} else {
console_printf("%d tx, %d rx, %.1f%% loss, latency min %d ms, max %d ms\n",
result.sent,
result.received,
result.loss_pt,
result.min_time_ms,
result.max_time_ms);
}
return 0;
}
static int cmd_ip_static_set(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_str *ip;
struct arg_str *gw;
struct arg_str *mask;
struct arg_str *dns;
struct arg_str *cmd;
struct arg_end *end;
} args;
if (reg) {
args.ip = arg_str0("a", NULL, "<IP>", "Set IP address");
args.gw = arg_str0("g", NULL, "<GW>", "Set gateway address");
args.mask = arg_str0("n", NULL, "<MASK>", "Set netmask");
args.dns = arg_str0("d", NULL, "<DNS>", "Set DNS server IP");
args.cmd = arg_str0(NULL, NULL, "{enable|disable}", "Enable or disable static IP");
args.end = arg_end(1);
reg->argtable = &args;
reg->help = "Configure, enable/disable, or check config of static IP.";
return 0;
}
bool any_change = false;
if (args.ip->count) {
uint32_t a = 0;
if (!inet_aton(args.ip->sval[0], &a)) {
console_println("Invalid IP");
return CONSOLE_ERR_INVALID_ARG;
}
gSettings.static_ip = a; // aton output is already in network byte order
settings_persist(SETTINGS_static_ip);
any_change = true;
}
if (args.gw->count) {
uint32_t a = 0;
if (!inet_aton(args.gw->sval[0], &a)) {
console_println("Invalid GW");
return CONSOLE_ERR_INVALID_ARG;
}
gSettings.static_ip_gw = a; // aton output is already in network byte order
settings_persist(SETTINGS_static_ip_gw);
any_change = true;
}
if (args.mask->count) {
uint32_t a = 0;
if (!inet_aton(args.mask->sval[0], &a)) {
console_println("Invalid mask");
return CONSOLE_ERR_INVALID_ARG;
}
gSettings.static_ip_mask = a; // aton output is already in network byte order
settings_persist(SETTINGS_static_ip_mask);
any_change = true;
}
if (args.dns->count) {
uint32_t a = 0;
if (!inet_aton(args.dns->sval[0], &a)) {
console_println("Invalid DNS IP");
return CONSOLE_ERR_INVALID_ARG;
}
gSettings.static_dns = a; // aton output is already in network byte order
settings_persist(SETTINGS_static_dns);
any_change = true;
}
if (args.cmd->count) {
int b = parse_boolean_arg(args.cmd->sval[0]);
if (b < 0) return CONSOLE_ERR_INVALID_ARG;
gSettings.dhcp_enable = !b;
settings_persist(SETTINGS_dhcp_enable);
any_change = true;
}
console_printf("Static IP: %s\n", gSettings.dhcp_enable ? MSG_DISABLED : MSG_ENABLED);
console_printf("- IP: %s\n", inet_ntoa(gSettings.static_ip));
console_printf("- Mask: %s\n", inet_ntoa(gSettings.static_ip_mask));
console_printf("- Gateway: %s\n", inet_ntoa(gSettings.static_ip_gw));
console_printf("- DNS: %s\n", inet_ntoa(gSettings.static_dns));
if (any_change) {
console_println("Restart to apply changes.");
}
return 0;
}
static int cmd_ip_ntp(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_str *cmd;
struct arg_str *addr;
struct arg_end *end;
} cmd_args;
if (reg) {
cmd_args.cmd = arg_str0(NULL, NULL, "<cmd>", "Enable or disable autostart. Omit to check current state. start = start now");
cmd_args.addr = arg_str0("s", NULL, "<server>", "Set NTP server");
cmd_args.end = arg_end(2);
reg->argtable = &cmd_args;
reg->help = "Check or modify NTP client setting, or start it manually.";
return 0;
}
if (cmd_args.addr->count) {
strncpy(gSettings.ntp_srv, cmd_args.addr->sval[0], NTP_SRV_LEN);
gSettings.ntp_srv[NTP_SRV_LEN - 1] = 0;
settings_persist(SETTINGS_ntp_srv);
console_printf("NTP server set to %s\n", gSettings.ntp_srv);
}
if (cmd_args.cmd->count) {
if (streq(cmd_args.cmd->sval[0], "start")) {
bool started = sntp_cli_start();
if (started) {
console_printf("NTP client started manually.\n");
} else {
console_color_printf(COLOR_RED, "Start failed. Client may be already running.\n");
}
return 0;
}
int b = parse_boolean_arg(cmd_args.cmd->sval[0]);
if (b < 0) return CONSOLE_ERR_INVALID_ARG;
gSettings.ntp_enable = b;
settings_persist(SETTINGS_ntp_enable);
}
console_printf("Client status: %s\n", gSettings.ntp_enable ? MSG_ENABLED : MSG_DISABLED);
console_printf("NTP server: %s\n", gSettings.ntp_srv);
/* show the current date */
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
if(timeinfo.tm_year < (2016 - 1900)) {
console_printf("Device time is not valid.\n");
} else {
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
console_printf("The current UTC date/time is: %s\n", strftime_buf);
}
if (cmd_args.cmd->count) {
// if it was "start", we returned early.
console_printf("Restart to apply changes\n");
}
return 0;
}
void register_cmd_ip(void)
{
console_group_add("ip", "IP status and settings");
console_cmd_register(cmd_ip_status, "ip status");
console_cmd_register(cmd_ip_pingwd, "ip wd");
console_cmd_register(cmd_ip_ntp, "ip ntp");
console_cmd_register(cmd_ip_ping, "ip ping");
console_cmd_register(cmd_ip_static_set, "ip static");
// this may be used for shortcuts like "ip in"
console_cmd_add_alias_fn(cmd_ip_status, "ip info");
}

@ -0,0 +1,52 @@
//
// Set telnet pw
//
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <esp_wifi.h>
#include <settings.h>
#include "console/cmd_common.h"
#include <console/cmddef.h>
#include <application.h>
#include <console/prefix_match.h>
static int cmd_clear(console_ctx_t *ctx, cmd_signature_t *reg)
{
EMPTY_CMD_SETUP("Clear access password");
console_printf("Access password cleared.\n");
gSettings.console_pw[0] = 0;
settings_persist(SETTINGS_console_pw);
return 0;
}
static int cmd_set(console_ctx_t *ctx, cmd_signature_t *reg)
{
static struct {
struct arg_str *pw;
struct arg_end *end;
} args;
if (reg) {
args.pw = arg_str1(NULL, NULL, "<password>", EXPENDABLE_STRING("New password"));
args.end = arg_end(1);
reg->argtable = &args;
reg->help = EXPENDABLE_STRING("Set access password");
return CONSOLE_OK;
}
strncpy(gSettings.console_pw, args.pw->sval[0], CONSOLE_PW_LEN - 1);
console_printf("Access pw set to: \"%.*s\"\n", CONSOLE_PW_LEN, args.pw->sval[0]);
settings_persist(SETTINGS_console_pw);
return 0;
}
void register_cmd_pw(void)
{
console_group_add("pw", "Access password");
console_cmd_register(cmd_set, "pw set");
console_cmd_register(cmd_clear, "pw clear");
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save