commit
						ddaa193821
					
				| @ -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 @@ | ||||
| # Use defaults
 | ||||
| @ -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 @@ | ||||
| # Use defaults.
 | ||||
| @ -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 @@ | ||||
| modbus slave | ||||
| @ -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…
					
					
				
		Reference in new issue