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) |
||||
{ |
||||