Browse Source

initial commit

master
Wesley Kerfoot 2 years ago
commit
091a6a89f3
  1. 6
      .gitignore
  2. 6
      CMakeLists.txt
  3. 90
      README.md
  4. 2
      components/cjson/CMakeLists.txt
  5. 3119
      components/cjson/cjson.c
  6. 4
      components/cjson/component.mk
  7. 300
      components/cjson/include/cjson.h
  8. 3
      components/esp8266_wrapper/CMakeLists.txt
  9. 4
      components/esp8266_wrapper/component.mk
  10. 193
      components/esp8266_wrapper/esp8266_wrapper.c
  11. 108
      components/esp8266_wrapper/include/esp8266_wrapper.h
  12. 3
      components/sht3x/CMakeLists.txt
  13. 4
      components/sht3x/component.mk
  14. 280
      components/sht3x/include/sht3x.h
  15. 58
      components/sht3x/include/sht3x_platform.h
  16. 450
      components/sht3x/sht3x.c
  17. 20
      example_test.py
  18. 3
      main/CMakeLists.txt
  19. 731
      main/plant_water.c
  20. 61
      main/plant_water.h
  21. 93
      main/protocol_examples_common.h

6
.gitignore

@ -0,0 +1,6 @@
.cache
build
main/build
main/Kconfig.projbuild
sdkconfig
sdkconfig.old

6
CMakeLists.txt

@ -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.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(plant_water)

90
README.md

@ -0,0 +1,90 @@
# Example: GPIO
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This test code shows how to configure GPIO and how to use it with interruption.
## GPIO functions:
| GPIO | Direction | Configuration |
| ---------------------------- | --------- | ------------------------------------------------------ |
| CONFIG_GPIO_OUTPUT_0 | output | |
| CONFIG_GPIO_OUTPUT_1 | output | |
| CONFIG_GPIO_INPUT_0 | input | pulled up, interrupt from rising edge and falling edge |
| CONFIG_GPIO_INPUT_1 | input | pulled up, interrupt from rising edge |
## Test:
1. Connect CONFIG_GPIO_OUTPUT_0 with CONFIG_GPIO_INPUT_0
2. Connect CONFIG_GPIO_OUTPUT_1 with CONFIG_GPIO_INPUT_1
3. Generate pulses on CONFIG_GPIO_OUTPUT_0/1, that triggers interrupt on CONFIG_GPIO_INPUT_0/1
**Note:** The following pin assignments are used by default, you can change them by `idf.py menuconfig` > `Example Configuration`.
| | CONFIG_GPIO_OUTPUT_0 | CONFIG_GPIO_OUTPUT_1 | CONFIG_GPIO_INPUT_0 | CONFIG_GPIO_INPUT_1 |
| --------------- | -------------------- | -------------------- | ------------------- | ------------------- |
| ESP32-C2/ESP32H2| 8 | 9 | 4 | 5 |
| All other chips | 18 | 19 | 4 | 5 |
## How to use example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
### Hardware Required
* A development board with any Espressif SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming
* Some jumper wires to connect GPIOs.
### Configure the project
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
As you run the example, you will see the following log:
```
I (317) gpio: GPIO[18]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (327) gpio: GPIO[19]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (337) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:1
I (347) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:1
Minimum free heap size: 289892 bytes
cnt: 0
cnt: 1
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
cnt: 2
GPIO[4] intr, val: 0
cnt: 3
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
cnt: 4
GPIO[4] intr, val: 0
cnt: 5
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
cnt: 6
GPIO[4] intr, val: 0
cnt: 7
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
cnt: 8
GPIO[4] intr, val: 0
cnt: 9
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
cnt: 10
...
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

2
components/cjson/CMakeLists.txt

@ -0,0 +1,2 @@
idf_component_register(SRCS "cjson.c"
INCLUDE_DIRS "include")

3119
components/cjson/cjson.c

File diff suppressed because it is too large

4
components/cjson/component.mk

@ -0,0 +1,4 @@
#
# Component makefile.
#
COMPONENT_ADD_INCLUDEDIRS := .

300
components/cjson/include/cjson.h

@ -0,0 +1,300 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
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.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 15
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif

3
components/esp8266_wrapper/CMakeLists.txt

@ -0,0 +1,3 @@
idf_component_register(SRCS "esp8266_wrapper.c"
INCLUDE_DIRS "include"
REQUIRES "driver")

4
components/esp8266_wrapper/component.mk

@ -0,0 +1,4 @@
#
# Component makefile.
#
COMPONENT_ADD_INCLUDEDIRS := .

193
components/esp8266_wrapper/esp8266_wrapper.c

@ -0,0 +1,193 @@
/**
* Wrapper module for source code compatibility with esp-open-rtos.
*/
#ifdef ESP_PLATFORM // ESP32 (ESP-IDF)
#include <sys/time.h>
#include <string.h>
#include "driver/spi_master.h"
#include "driver/spi_common.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp8266_wrapper.h"
// esp-open-rtos SDK function wrapper
uint32_t sdk_system_get_time () {
struct timeval time;
gettimeofday(&time,0);
return (uint32_t) (time.tv_sec*(long int)1e6 + time.tv_usec);
}
bool gpio_isr_service_installed = false;
bool auto_pull_up = false;
bool auto_pull_down = true;
esp_err_t gpio_set_interrupt(gpio_num_t gpio,
gpio_int_type_t type,
gpio_interrupt_handler_t handler)
{
if (!gpio_isr_service_installed)
gpio_isr_service_installed = (gpio_install_isr_service(0) == ESP_OK);
gpio_config_t gpio_cfg = {
.pin_bit_mask = ((uint64_t)(((uint64_t)1)<< gpio)),
.mode = GPIO_MODE_INPUT,
.pull_up_en = auto_pull_up,
.pull_down_en = auto_pull_down,
.intr_type = type
};
gpio_config(&gpio_cfg);
// set interrupt handler
gpio_isr_handler_add(gpio, (gpio_isr_t)handler, (void*)gpio);
return ESP_OK;
}
void gpio_enable (gpio_num_t gpio, const gpio_mode_t mode)
{
gpio_config_t gpio_cfg = {
.pin_bit_mask = ((uint64_t)(((uint64_t)1)<< gpio)),
.mode = mode,
.pull_up_en = auto_pull_up,
.pull_down_en = auto_pull_down,
};
gpio_config(&gpio_cfg);
}
// esp-open-rtos I2C interface wrapper
#define I2C_ACK_VAL 0x0
#define I2C_NACK_VAL 0x1
void i2c_init (int bus, gpio_num_t scl, gpio_num_t sda, uint32_t freq)
{
i2c_config_t conf = {0};
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = sda;
conf.scl_io_num = scl;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = freq;
conf.clk_flags = 0;
i2c_param_config(bus, &conf);
i2c_driver_install(bus, I2C_MODE_MASTER, 0, 0, 0);
}
int i2c_slave_write (uint8_t bus, uint8_t addr, const uint8_t *reg,
uint8_t *data, uint32_t len)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, addr << 1 | I2C_MASTER_WRITE, true);
if (reg)
i2c_master_write_byte(cmd, *reg, true);
if (data)
i2c_master_write(cmd, data, len, true);
i2c_master_stop(cmd);
esp_err_t err = i2c_master_cmd_begin(bus, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return err;
}
int i2c_slave_read (uint8_t bus, uint8_t addr, const uint8_t *reg,
uint8_t *data, uint32_t len)
{
if (len == 0) return true;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
if (reg)
{
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( addr << 1 ) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, *reg, true);
if (!data)
i2c_master_stop(cmd);
}
if (data)
{
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( addr << 1 ) | I2C_MASTER_READ, true);
if (len > 1) i2c_master_read(cmd, data, len-1, I2C_ACK_VAL);
i2c_master_read_byte(cmd, data + len-1, I2C_NACK_VAL);
i2c_master_stop(cmd);
}
esp_err_t err = i2c_master_cmd_begin(bus, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return err;
}
// esp-open-rtos SPI interface wrapper
#define SPI_MAX_BUS 3 // ESP32 features three SPIs (SPI_HOST, HSPI_HOST and VSPI_HOST)
#define SPI_MAX_CS 34 // GPIO 33 is the last port that can be used as output
spi_device_handle_t spi_handles[SPI_MAX_CS] = { 0 };
bool spi_bus_init (spi_host_device_t host, uint8_t sclk , uint8_t miso, uint8_t mosi)
{
spi_bus_config_t spi_bus_cfg = {
.miso_io_num=miso,
.mosi_io_num=mosi,
.sclk_io_num=sclk,
.quadwp_io_num=-1,
.quadhd_io_num=-1
};
return (spi_bus_initialize(host, &spi_bus_cfg, 1) == ESP_OK);
}
bool spi_device_init (uint8_t bus, uint8_t cs)
{
if (bus >= SPI_MAX_BUS || cs >= SPI_MAX_CS)
return false;
if ((spi_handles[cs] = malloc (sizeof(spi_device_handle_t))) == 0)
return false;
spi_device_interface_config_t dev_cfg = {
.clock_speed_hz = 1e6, // 1 MHz clock
.mode = 0, // SPI mode 0
.spics_io_num = cs, // CS GPIO
.queue_size = 1,
.flags = 0, // no flags set
.command_bits = 0, // no command bits used
.address_bits = 0, // register address is first byte in MOSI
.dummy_bits = 0 // no dummy bits used
};
if (spi_bus_add_device(bus, &dev_cfg, &(spi_handles[cs])) != ESP_OK)
{
free (spi_handles[cs]);
return false;
}
return true;
}
size_t spi_transfer_pf (uint8_t bus, uint8_t cs, const uint8_t *mosi, uint8_t *miso, uint16_t len)
{
spi_transaction_t spi_trans;
if (cs >= SPI_MAX_CS)
return 0;
memset(&spi_trans, 0, sizeof(spi_trans)); // zero out spi_trans;
spi_trans.tx_buffer = mosi;
spi_trans.rx_buffer = miso;
spi_trans.length=len*8;
if (spi_device_transmit(spi_handles[cs], &spi_trans) != ESP_OK)
return 0;
return len;
}
#endif // ESP32 (ESP-IDF)

108
components/esp8266_wrapper/include/esp8266_wrapper.h

@ -0,0 +1,108 @@
/*
* Wrapper module for source code compatibility with esp-open-rtos.
*/
#ifndef __ESP8266_WRAPPER_H__
#define __ESP8266_WRAPPER_H__
#ifdef ESP_PLATFORM // ESP32 (ESP-IDF)
#ifdef __cplusplus
extern "C" {
#endif
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "driver/spi_common.h"
/*
* esp-open-rtos SDK function wrapper
*/
uint32_t sdk_system_get_time ();
#define uart_set_baud(p,r) uart_set_baudrate (p,r)
#define IRAM IRAM_ATTR
#define GPIO_INTTYPE_NONE GPIO_INTR_DISABLE
#define GPIO_INTTYPE_EDGE_POS GPIO_INTR_POSEDGE
#define GPIO_INTTYPE_EDGE_NEG GPIO_INTR_NEGEDGE
#define GPIO_INTTYPE_EDGE_ANY GPIO_INTR_ANYEDGE
#define GPIO_INTTYPE_LEVEL_LOW GPIO_INTR_LOW_LEVEL
#define GPIO_INTTYPE_LEVEL_HIGH GPIO_INTR_HIGH_LEVEL
// Set it true, if isr_service is already installed anywhere else or should
// not be installed, otherwise, *gpio_set_interrupt* install it when it is
// called first time.
extern bool gpio_isr_service_installed;
// pull-up, pull-down configuration used for next call of *gpio_set_interrupt*
extern bool auto_pull_up; // default false;
extern bool auto_pull_down; // default true;
// ISR handler type compatible to the ESP8266
typedef void (*gpio_interrupt_handler_t)(uint8_t gpio);
// GPIO set interrupt function compatible to ESP8266
esp_err_t gpio_set_interrupt(gpio_num_t gpio,
gpio_int_type_t type,
gpio_interrupt_handler_t handler);
// GPIO enable function compatible to esp-open-rtos
#define GPIO_INPUT GPIO_MODE_INPUT
#define GPIO_OUTPUT GPIO_MODE_OUTPUT
#define GPIO_OUT_OPEN_DRAIN GPIO_MODE_OUTPUT_OD
void gpio_enable (gpio_num_t gpio, const gpio_mode_t mode);
/*
* esp-open-rtos I2C interface wrapper
*/
#define I2C_FREQ_80K 80000
#define I2C_FREQ_100K 100000
#define I2C_FREQ_400K 400000
#define I2C_FREQ_500K 500000
#define I2C_FREQ_600K 600000
#define I2C_FREQ_800K 800000
#define I2C_FREQ_1000K 1000000
#define I2C_FREQ_1300K 1300000
#define i2c_set_clock_stretch(bus,cs) // not needed on ESP32
void i2c_init (int bus, gpio_num_t scl, gpio_num_t sda, uint32_t freq);
int i2c_slave_write (uint8_t bus, uint8_t addr, const uint8_t *reg,
uint8_t *data, uint32_t len);
int i2c_slave_read (uint8_t bus, uint8_t addr, const uint8_t *reg,
uint8_t *data, uint32_t len);
/*
* esp-open-rtos SPI interface wrapper
*/
bool spi_bus_init (spi_host_device_t host,
uint8_t sclk , uint8_t miso, uint8_t mosi);
bool spi_device_init (uint8_t bus, uint8_t cs);
size_t spi_transfer_pf(uint8_t bus, uint8_t cs,
const uint8_t *mosi, uint8_t *miso, uint16_t len);
/*
* freertos api wrapper
*/
#ifdef __cplusplus
}
#endif
#endif // ESP_PLATFORM
#endif // __ESP8266_WRAPPER_H__

3
components/sht3x/CMakeLists.txt

@ -0,0 +1,3 @@
idf_component_register(SRCS "sht3x.c"
INCLUDE_DIRS "include"
REQUIRES "esp8266_wrapper")

4
components/sht3x/component.mk

@ -0,0 +1,4 @@
#
# Component makefile.
#
COMPONENT_ADD_INCLUDEDIRS := .

280
components/sht3x/include/sht3x.h

@ -0,0 +1,280 @@
/*
* Driver for Sensirion SHT3x digital temperature and humidity sensor
* connected to I2C
*
* This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos)
* [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32
* and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux
* based systems using a wrapper library for ESP8266 functions.
*
* ----------------------------------------------------------------
*
* The BSD License (3-clause license)
*
* Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
* 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.
*
* 3. Neither the name of the copyright holder 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 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 __SHT3x_H__
#define __SHT3x_H__
// Uncomment to enable debug output
// #define SHT3x_DEBUG_LEVEL_1 // only error messages
// #define SHT3x_DEBUG_LEVEL_2 // error and debug messages
#include "stdint.h"
#include "stdbool.h"
#include "sht3x_platform.h"
#ifdef __cplusplus
extern "C" {
#endif
// definition of possible I2C slave addresses
#define SHT3x_ADDR_1 0x44 // ADDR pin connected to GND/VSS (default)
#define SHT3x_ADDR_2 0x45 // ADDR pin connected to VDD
// definition of error codes
#define SHT3x_OK 0
#define SHT3x_NOK -1
#define SHT3x_I2C_ERROR_MASK 0x000f
#define SHT3x_DRV_ERROR_MASK 0xfff0
// error codes for I2C interface ORed with SHT3x error codes
#define SHT3x_I2C_READ_FAILED 1
#define SHT3x_I2C_SEND_CMD_FAILED 2
#define SHT3x_I2C_BUSY 3
// SHT3x driver error codes OR ed with error codes for I2C interface
#define SHT3x_MEAS_NOT_STARTED (1 << 8)
#define SHT3x_MEAS_ALREADY_RUNNING (2 << 8)
#define SHT3x_MEAS_STILL_RUNNING (3 << 8)
#define SHT3x_READ_RAW_DATA_FAILED (4 << 8)
#define SHT3x_SEND_MEAS_CMD_FAILED (5 << 8)
#define SHT3x_SEND_RESET_CMD_FAILED (6 << 8)
#define SHT3x_SEND_STATUS_CMD_FAILED (7 << 8)
#define SHT3x_SEND_FETCH_CMD_FAILED (8 << 8)
#define SHT3x_WRONG_CRC_TEMPERATURE (9 << 8)
#define SHT3x_WRONG_CRC_HUMIDITY (10 << 8)
#define SHT3x_RAW_DATA_SIZE 6
/**
* @brief raw data type
*/
typedef uint8_t sht3x_raw_data_t [SHT3x_RAW_DATA_SIZE];
/**
* @brief possible measurement modes
*/
typedef enum {
sht3x_single_shot = 0, // one single measurement
sht3x_periodic_05mps, // periodic with 0.5 measurements per second (mps)
sht3x_periodic_1mps, // periodic with 1 measurements per second (mps)
sht3x_periodic_2mps, // periodic with 2 measurements per second (mps)
sht3x_periodic_4mps, // periodic with 4 measurements per second (mps)
sht3x_periodic_10mps // periodic with 10 measurements per second (mps)
} sht3x_mode_t;
/**
* @brief possible repeatability modes
*/
typedef enum {
sht3x_high = 0,
sht3x_medium,
sht3x_low
} sht3x_repeat_t;
/**
* @brief SHT3x sensor device data structure type
*/
typedef struct {
uint32_t error_code; // combined error codes
uint8_t bus; // I2C bus at which sensor is connected
uint8_t addr; // I2C slave address of the sensor
sht3x_mode_t mode; // used measurement mode
sht3x_repeat_t repeatability; // used repeatability
bool meas_started; // indicates whether measurement started
uint32_t meas_start_time; // measurement start time in us
bool meas_first; // first measurement in periodic mode
} sht3x_sensor_t;
/**
* @brief Initialize a SHT3x sensor
*
* The function creates a data structure describing the sensor and
* initializes the sensor device.
*
* @param bus I2C bus at which the sensor is connected
* @param addr I2C slave address of the sensor
* @return pointer to sensor data structure, or NULL on error
*/
sht3x_sensor_t* sht3x_init_sensor (uint8_t bus, uint8_t addr);
/**
* @brief High level measurement function
*
* For convenience this function comprises all three steps to perform
* one measurement in only one function:
*
* 1. Starts a measurement in single shot mode with high reliability
* 2. Waits using *vTaskDelay* until measurement results are available
* 3. Returns the results in kind of floating point sensor values
*
* This function is the easiest way to use the sensor. It is most suitable
* for users that don't want to have the control on sensor details.
*
* Please note: The function delays the calling task up to 30 ms to wait for
* the the measurement results. This might lead to problems when the function
* is called from a software timer callback function.
*
* @param dev pointer to sensor device data structure
* @param temperature returns temperature in degree Celsius
* @param humidity returns humidity in percent
* @return true on success, false on error
*/
bool sht3x_measure (sht3x_sensor_t* dev, float* temperature, float* humidity);
/**
* @brief Start the measurement in single shot or periodic mode
*
* The function starts the measurement either in *single shot mode*
* (exactly one measurement) or *periodic mode* (periodic measurements)
* with given repeatabilty.
*
* In the *single shot mode*, this function has to be called for each
* measurement. The measurement duration has to be waited every time
* before the results can be fetched.
*
* In the *periodic mode*, this function has to be called only once. Also
* the measurement duration has to be waited only once until the first
* results are available. After this first measurement, the sensor then
* automatically performs all subsequent measurements. The rate of periodic
* measurements can be 10, 4, 2, 1 or 0.5 measurements per second (mps).
*
* Please note: Due to inaccuracies in timing of the sensor, the user task
* should fetch the results at a lower rate. The rate of the periodic
* measurements is defined by the parameter *mode*.
*
* @param dev pointer to sensor device data structure
* @param mode measurement mode, see type *sht3x_mode_t*
* @param repeat repeatability, see type *sht3x_repeat_t*
* @return true on success, false on error
*/
bool sht3x_start_measurement (sht3x_sensor_t* dev, sht3x_mode_t mode,
sht3x_repeat_t repeat);
/**
* @brief Get the duration of a measurement in RTOS ticks.
*
* The function returns the duration in RTOS ticks required by the sensor to
* perform a measurement for the given repeatability. Once a measurement is
* started with function *sht3x_start_measurement* the user task can use this
* duration in RTOS ticks directly to wait with function *vTaskDelay* until
* the measurement results can be fetched.
*
* Please note: The duration only depends on repeatability level. Therefore,
* it can be considered as constant for a repeatibility.
*
* @param repeat repeatability, see type *sht3x_repeat_t*
* @return measurement duration given in RTOS ticks
*/
uint8_t sht3x_get_measurement_duration (sht3x_repeat_t repeat);
/**
* @brief Read measurement results from sensor as raw data
*
* The function read measurement results from the sensor, checks the CRC
* checksum and stores them in the byte array as following.
*
* data[0] = Temperature MSB
* data[1] = Temperature LSB
* data[2] = Temperature CRC
* data[3] = Pressure MSB
* data[4] = Pressure LSB
* data[2] = Pressure CRC
*
* In case that there are no new data that can be read, the function fails.
*
* @param dev pointer to sensor device data structure
* @param raw_data byte array in which raw data are stored
* @return true on success, false on error
*/
bool sht3x_get_raw_data(sht3x_sensor_t* dev, sht3x_raw_data_t raw_data);
/**
* @brief Computes sensor values from raw data
*
* @param raw_data byte array that contains raw data
* @param temperature returns temperature in degree Celsius
* @param humidity returns humidity in percent
* @return true on success, false on error
*/
bool sht3x_compute_values (sht3x_raw_data_t raw_data,
float* temperature, float* humidity);
/**
* @brief Get measurement results in form of sensor values
*
* The function combines function *sht3x_read_raw_data* and function
* *sht3x_compute_values* to get the measurement results.
*
* In case that there are no results that can be read, the function fails.
*
* @param dev pointer to sensor device data structure
* @param temperature returns temperature in degree Celsius
* @param humidity returns humidity in percent
* @return true on success, false on error
*/
bool sht3x_get_results (sht3x_sensor_t* dev,
float* temperature, float* humidity);
#ifdef __cplusplus
}
#endif
#endif /* __SHT3x_H__ */

58
components/sht3x/include/sht3x_platform.h

@ -0,0 +1,58 @@
/*
* Driver for AMS CCS811 digital gas sensor connected to I2C.
*
* This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos)
* [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32
* and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux
* based systems using a wrapper library for ESP8266 functions.
*
* ---------------------------------------------------------------------------
*
* The BSD License (3-clause license)
*
* Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
* 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.
*
* 3. Neither the name of the copyright holder 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 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.
*/
/**
* Platform file: platform specific definitions, includes and functions
*/
#ifndef __CCS811_PLATFORM_H__
#define __CCS811_PLATFORM_H__
#ifdef ESP_PLATFORM // ESP32 (ESP-IDF)
// platform specific includes
#include "esp8266_wrapper.h"
#include <errno.h>
#endif // ESP_PLATFORM
#endif // __CCS811_PLATFORM_H__

450
components/sht3x/sht3x.c

@ -0,0 +1,450 @@
/*
* Driver for Sensirion SHT3x digital temperature and humidity sensor
* connected to I2C
*
* This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos)
* [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32
* and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux
* based systems using a wrapper library for ESP8266 functions.
*
* ----------------------------------------------------------------
*
* The BSD License (3-clause license)
*
* Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
* 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.
*
* 3. Neither the name of the copyright holder 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 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.
*/
#include <string.h>
#include <stdlib.h>
#include "sht3x.h"
#define SHT3x_STATUS_CMD 0xF32D
#define SHT3x_CLEAR_STATUS_CMD 0x3041
#define SHT3x_RESET_CMD 0x30A2
#define SHT3x_FETCH_DATA_CMD 0xE000
#define SHT3x_HEATER_OFF_CMD 0x3066
const uint16_t SHT3x_MEASURE_CMD[6][3] = {
{0x2400,0x240b,0x2416}, // [SINGLE_SHOT][H,M,L] without clock stretching
{0x2032,0x2024,0x202f}, // [PERIODIC_05][H,M,L]
{0x2130,0x2126,0x212d}, // [PERIODIC_1 ][H,M,L]
{0x2236,0x2220,0x222b}, // [PERIODIC_2 ][H,M,L]
{0x2234,0x2322,0x2329}, // [PERIODIC_4 ][H,M,L]
{0x2737,0x2721,0x272a} }; // [PERIODIC_10][H,M,L]
// due to the fact that ticks can be smaller than portTICK_PERIOD_MS, one and
// a half tick period added to the duration to be sure that waiting time for
// the results is long enough
#define TIME_TO_TICKS(ms) (1 + ((ms) + (portTICK_PERIOD_MS-1) + portTICK_PERIOD_MS/2 ) / portTICK_PERIOD_MS)
#define SHT3x_MEAS_DURATION_REP_HIGH 15
#define SHT3x_MEAS_DURATION_REP_MEDIUM 6
#define SHT3x_MEAS_DURATION_REP_LOW 4
// measurement durations in us
const uint16_t SHT3x_MEAS_DURATION_US[3] = { SHT3x_MEAS_DURATION_REP_HIGH * 1000,
SHT3x_MEAS_DURATION_REP_MEDIUM * 1000,
SHT3x_MEAS_DURATION_REP_LOW * 1000 };
// measurement durations in RTOS ticks
const uint8_t SHT3x_MEAS_DURATION_TICKS[3] = { TIME_TO_TICKS(SHT3x_MEAS_DURATION_REP_HIGH),
TIME_TO_TICKS(SHT3x_MEAS_DURATION_REP_MEDIUM),
TIME_TO_TICKS(SHT3x_MEAS_DURATION_REP_LOW) };
#if defined(SHT3x_DEBUG_LEVEL_2)
#define debug(s, f, ...) printf("%s %s: " s "\n", "SHT3x", f, ## __VA_ARGS__)
#define debug_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "SHT3x", f, d->bus, d->addr, ## __VA_ARGS__)
#else
#define debug(s, f, ...)
#define debug_dev(s, f, d, ...)
#endif
#if defined(SHT3x_DEBUG_LEVEL_1) || defined(SHT3x_DEBUG_LEVEL_2)
#define error(s, f, ...) printf("%s %s: " s "\n", "SHT3x", f, ## __VA_ARGS__)
#define error_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "SHT3x", f, d->bus, d->addr, ## __VA_ARGS__)
#else
#define error(s, f, ...)
#define error_dev(s, f, d, ...)
#endif
/** Forward declaration of function for internal use */
static bool sht3x_is_measuring (sht3x_sensor_t*);
static bool sht3x_send_command (sht3x_sensor_t*, uint16_t);
static bool sht3x_read_data (sht3x_sensor_t*, uint8_t*, uint32_t);
static bool sht3x_get_status (sht3x_sensor_t*, uint16_t*);
static bool sht3x_reset (sht3x_sensor_t*);
static uint8_t crc8 (uint8_t data[], int len);
/** ------------------------------------------------ */
bool sht3x_init_driver()
{
return true;
}
sht3x_sensor_t* sht3x_init_sensor(uint8_t bus, uint8_t addr)
{
sht3x_sensor_t* dev;
if ((dev = malloc (sizeof(sht3x_sensor_t))) == NULL) {
printf("Failed to allocate memory\n");
return NULL;
}
// inititalize sensor data structure
dev->bus = bus;
dev->addr = addr;
dev->mode = sht3x_single_shot;
dev->meas_start_time = 0;
dev->meas_started = false;
dev->meas_first = false;
uint16_t status;
// try to reset the sensor
if (!sht3x_reset(dev))
{
printf("could not reset the sensor\n");
}
// check again the status after clear status command
if (!sht3x_get_status(dev, &status))
{
printf("could not get sensor status\n");
free(dev);
return NULL;
}
printf("sensor initialized\n");
return dev;
}
bool sht3x_measure (sht3x_sensor_t* dev, float* temperature, float* humidity)
{
if (!dev || (!temperature && !humidity)) return false;
if (!sht3x_start_measurement (dev, sht3x_single_shot, sht3x_high)) {
printf("Could not start measurement\n");
return false;
}
vTaskDelay (SHT3x_MEAS_DURATION_TICKS[sht3x_high]);
sht3x_raw_data_t raw_data;
if (!sht3x_get_raw_data (dev, raw_data)) {
printf("Could not get raw data\n");
return false;
}
return sht3x_compute_values (raw_data, temperature, humidity);
}
bool sht3x_start_measurement (sht3x_sensor_t* dev, sht3x_mode_t mode, sht3x_repeat_t repeat)
{
if (!dev) return false;
dev->error_code = SHT3x_OK;
dev->mode = mode;
dev->repeatability = repeat;
// start measurement according to selected mode and return an duration estimate
if (!sht3x_send_command(dev, SHT3x_MEASURE_CMD[mode][repeat]))
{
error_dev ("could not send start measurment command", __FUNCTION__, dev);
dev->error_code |= SHT3x_SEND_MEAS_CMD_FAILED;
return false;
}
dev->meas_start_time = sdk_system_get_time ();
printf("start time = %d\n", dev->meas_start_time);
dev->meas_started = true;
dev->meas_first = true;
return true;
}
uint8_t sht3x_get_measurement_duration (sht3x_repeat_t repeat)
{
return SHT3x_MEAS_DURATION_TICKS[repeat]; // in RTOS ticks
}
bool sht3x_get_raw_data(sht3x_sensor_t* dev, sht3x_raw_data_t raw_data)
{
if (!dev || !raw_data) {
printf("dev = %p, raw_data = %p\n", dev, raw_data);
return false;
}
dev->error_code = SHT3x_OK;
if (!dev->meas_started)
{
printf ("measurement is not started\n");
dev->error_code = SHT3x_MEAS_NOT_STARTED;
return sht3x_is_measuring (dev);
}
if (sht3x_is_measuring(dev))
{
printf("measurement is still running\n");
dev->error_code = SHT3x_MEAS_STILL_RUNNING;
return false;
}
// send fetch command in any periodic mode (mode > 0) before read raw data
if (dev->mode && !sht3x_send_command(dev, SHT3x_FETCH_DATA_CMD))
{
printf ("send fetch command failed\n");
dev->error_code |= SHT3x_SEND_FETCH_CMD_FAILED;
return false;
}
// read raw data
if (!sht3x_read_data(dev, raw_data, sizeof(sht3x_raw_data_t)))
{
printf("read raw data failed\n");
dev->error_code |= SHT3x_READ_RAW_DATA_FAILED;
return false;
}
// reset first measurement flag
dev->meas_first = false;
// reset measurement started flag in single shot mode
if (dev->mode == sht3x_single_shot)
dev->meas_started = false;
// check temperature crc
if (crc8(raw_data,2) != raw_data[2])
{
printf("CRC check for temperature data failed\n");
dev->error_code |= SHT3x_WRONG_CRC_TEMPERATURE;
return false;
}
// check humidity crc
if (crc8(raw_data+3,2) != raw_data[5])
{
printf ("CRC check for humidity data failed\n");
dev->error_code |= SHT3x_WRONG_CRC_HUMIDITY;
return false;
}
return true;
}
bool sht3x_compute_values (sht3x_raw_data_t raw_data, float* temperature, float* humidity)
{
if (!raw_data) {
printf("raw_data was falsey\n");
return false;
}
if (temperature)
*temperature = ((((raw_data[0] * 256.0) + raw_data[1]) * 175) / 65535.0) - 45;
if (humidity)
*humidity = ((((raw_data[3] * 256.0) + raw_data[4]) * 100) / 65535.0);
return true;
}
bool sht3x_get_results (sht3x_sensor_t* dev, float* temperature, float* humidity)
{
if (!dev || (!temperature && !humidity)) return false;
sht3x_raw_data_t raw_data;
if (!sht3x_get_raw_data (dev, raw_data))
return false;
return sht3x_compute_values (raw_data, temperature, humidity);
}
/* Functions for internal use only */
static bool sht3x_is_measuring (sht3x_sensor_t* dev)
{
if (!dev) return false;
dev->error_code = SHT3x_OK;
// not running if measurement is not started at all or
// it is not the first measurement in periodic mode
if (!dev->meas_started || !dev->meas_first)
return false;
// not running if time elapsed is greater than duration
printf("start time = %d\n", dev->meas_start_time);
printf("current time = %d\n", sdk_system_get_time());
printf("difference = %d\n", sdk_system_get_time() - dev->meas_start_time);
uint32_t elapsed = sdk_system_get_time() - dev->meas_start_time;
return elapsed < SHT3x_MEAS_DURATION_US[dev->repeatability];
}
static bool sht3x_send_command(sht3x_sensor_t* dev, uint16_t cmd)
{
if (!dev) {
printf("dev was falsey in sht3x_send_command\n");
return false;
}
uint8_t data[2] = { cmd >> 8, cmd & 0xff };
printf("send command MSB=%02x LSB=%02x\n", data[0], data[1]);
int err = i2c_slave_write(dev->bus, dev->addr, 0, data, 2);
if (err)
{
dev->error_code |= (err == -EBUSY) ? SHT3x_I2C_BUSY : SHT3x_I2C_SEND_CMD_FAILED;
printf ("i2c error %d on write command %02x\n", err, cmd);
printf("err = %d\n", err);
printf("busy = %d\n", -EBUSY);
return false;
}
return true;
}
static bool sht3x_read_data(sht3x_sensor_t* dev, uint8_t *data, uint32_t len)
{
if (!dev) return false;
int err = i2c_slave_read(dev->bus, dev->addr, 0, data, len);
if (err)
{
dev->error_code |= (err == -EBUSY) ? SHT3x_I2C_BUSY : SHT3x_I2C_READ_FAILED;
error_dev ("error %d on read %d byte", __FUNCTION__, dev, err, len);
return false;
}
# ifdef SHT3x_DEBUG_LEVEL_2
printf("SHT3x %s: bus %d, addr %02x - read following bytes: ",
__FUNCTION__, dev->bus, dev->addr);
for (int i=0; i < len; i++)
printf("%02x ", data[i]);
printf("\n");
# endif // ifdef SHT3x_DEBUG_LEVEL_2
return true;
}
static bool sht3x_reset (sht3x_sensor_t* dev)
{
if (!dev) {
printf("dev was falsey\n");
return false;
}
printf("soft-reset triggered\n");
dev->error_code = SHT3x_OK;
// send reset command
if (!sht3x_send_command(dev, SHT3x_RESET_CMD))
{
printf("Got error code from sht3x_send_command\n");
dev->error_code |= SHT3x_SEND_RESET_CMD_FAILED;
return false;
}
// wait for small amount of time needed (according to datasheet 0.5ms)
vTaskDelay (100 / portTICK_PERIOD_MS);
uint16_t status;
// check the status after reset
if (!sht3x_get_status(dev, &status)) {
printf("Failed to get status from sensor\n");
return false;
}
return true;
}
static bool sht3x_get_status (sht3x_sensor_t* dev, uint16_t* status)
{
if (!dev || !status) return false;
dev->error_code = SHT3x_OK;
uint8_t data[3];
if (!sht3x_send_command(dev, SHT3x_STATUS_CMD) || !sht3x_read_data(dev, data, 3))
{
dev->error_code |= SHT3x_SEND_STATUS_CMD_FAILED;
return false;
}
*status = data[0] << 8 | data[1];
debug_dev ("status=%02x", __FUNCTION__, dev, *status);
return true;
}
const uint8_t g_polynom = 0x31;
static uint8_t crc8 (uint8_t data[], int len)
{
// initialization value
uint8_t crc = 0xff;
// iterate over all bytes
for (int i=0; i < len; i++)
{
crc ^= data[i];
for (int i = 0; i < 8; i++)
{
bool xor = crc & 0x80;
crc = crc << 1;
crc = xor ? crc ^ g_polynom : crc;
}
}
return crc;
}

20
example_test.py

@ -0,0 +1,20 @@
#!/usr/bin/env python
from __future__ import division, print_function, unicode_literals
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_TWAI1', target=['esp32', 'esp32s2'], ci_target=['esp32'])
def test_examples_gpio(env, extra_data):
app_name = 'gpio'
dut = env.get_dut(app_name, 'examples/peripherals/gpio/generic_gpio')
dut.start_app()
res = dut.expect(ttfw_idf.MINIMUM_FREE_HEAP_SIZE_RE)
if not res:
raise ValueError('Maximum heap size info not found')
ttfw_idf.print_heap_size(app_name, dut.app.config_name, dut.TARGET, res[0])
if __name__ == '__main__':
test_examples_gpio()

3
main/CMakeLists.txt

@ -0,0 +1,3 @@
idf_component_register(SRCS "plant_water.c"
INCLUDE_DIRS "."
REQUIRES "esp_http_server" "nvs_flash" "esp_http_client" "esp_eth" "driver" "esp8266_wrapper" "sht3x" "cjson")

731
main/plant_water.c

@ -0,0 +1,731 @@
#include "./plant_water.h"
#include "sht3x.h"
#include "cjson.h"
#define I2C_BUS 0
#define I2C_SCL_PIN 22
#define I2C_SDA_PIN 21
#define HTTPD_RESP_SIZE 100
#define MAX_CRON_SPECS 5
static const char *TAG = "pump";
static sht3x_sensor_t* sensor;
static void set_pump(int pump_num, int state) {
// Set duty to 100%
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, pump_num, state == 1 ? LEDC_DUTY: 0));
// Update duty to apply the new value
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, pump_num));
}
static void
pump_one_on() {
set_pump(0, 1);
set_pump(1, 0);
}
static void
pump_two_on() {
set_pump(0, 0);
set_pump(1, 1);
}
static void
pumps_off() {
set_pump(0, 0);
set_pump(1, 0);
}
typedef enum {
PUMP_ON = 1,
PUMP_OFF = 2,
} event_type;
struct event_t {
event_type pump_1;
event_type pump_2;
int pump_delay;
};
struct cron_t {
int pump_num;
int state;
int pump_on_time;
int hour;
int minute;
int last_ran_day; // day of the month it last ran
int last_ran_minute; // minute it last ran
int last_ran_hour; // hour it last ran
};
static struct cron_t cron_specs[MAX_CRON_SPECS];
static TickType_t
make_delay(int seconds) {
return (1000*seconds) / portTICK_PERIOD_MS;
}
// Timer type definitions
const TickType_t PUMP_TIMER_DELAY = (1000*60) / portTICK_PERIOD_MS;
const TickType_t PUMP_CB_PERIOD = (1000*10) / portTICK_PERIOD_MS;
TimerHandle_t pumpTimer = 0; // Timer for toggling the pumps on/off
StaticTimer_t pumpTimerBuffer; // Memory backing for the timer, allocated statically
// Queue type definitions
uint8_t queueStorage[PUMP_EV_NUM*sizeof (struct event_t)]; // byte array for queue memory
static StaticQueue_t pumpEvents;
QueueHandle_t pumpEventsHandle;
uint8_t timerQueueStorage[PUMP_EV_NUM*sizeof (struct cron_t)]; // byte array for queue memory
static StaticQueue_t timerEvents;
QueueHandle_t timerEventsHandle;
// Task type definitions
StaticTask_t xTaskBuffer;
StackType_t xStack[TASK_STACK_SIZE];
void
pumpRunnerTaskFunction(void *params) {
struct event_t pumpMessage;
printf("Task started\n");
configASSERT( ( uint32_t ) params == 1UL );
while (1) {
vTaskDelay(2000 / portTICK_PERIOD_MS);
if (pumpEventsHandle != NULL) {
// The queue exists and is created
if (xQueueReceive(pumpEventsHandle, &pumpMessage, (TickType_t)PUMP_TIMER_DELAY) == pdPASS) {
printf("got a message, pump_1 = %u, pump_2 = %u, PUMP_ON = %u, PUMP_OFF = %u\n", pumpMessage.pump_1, pumpMessage.pump_2, PUMP_ON, PUMP_OFF);
if (pumpMessage.pump_1 == PUMP_ON) {
pump_one_on();
}
if (pumpMessage.pump_2 == PUMP_ON) {
pump_two_on();
}
vTaskDelay(pumpMessage.pump_delay);
pumps_off();
}
}
}
}
static void
createPumpRunnerTask(void) {
xTaskCreateStatic(pumpRunnerTaskFunction,
"pumpt",
TASK_STACK_SIZE,
(void*)1,
tskIDLE_PRIORITY + 2,
xStack,
&xTaskBuffer);
}
void
runPumps(int delay1, int delay2) {
struct event_t message;
message.pump_1 = PUMP_ON;
message.pump_2 = PUMP_OFF;
message.pump_delay = make_delay(delay1);
xQueueSend(pumpEventsHandle, (void*)&message, (TickType_t)0);
message.pump_2 = PUMP_ON;
message.pump_1 = PUMP_OFF;
message.pump_delay = make_delay(delay2);
xQueueSend(pumpEventsHandle, (void*)&message, (TickType_t)0);
}
void
pumpTimerCb(TimerHandle_t pumpTimer) {
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
struct cron_t pump_timer_config;
static int next_cron_spec = 0;
if (timerEventsHandle != NULL) {
if (xQueueReceive(timerEventsHandle, &pump_timer_config, (TickType_t)0) == pdPASS) {
printf("Got a new cron spec\n");
cron_specs[next_cron_spec] = pump_timer_config;
next_cron_spec = (next_cron_spec + 1) % MAX_CRON_SPECS;
printf("new: hour = %d, minute = %d\n", pump_timer_config.hour, pump_timer_config.minute);
}
}
// check current number of cron specs, remove oldest one and replace if none left
// loop over them and check time and execute commands
for (int i = 0; i < MAX_CRON_SPECS; i++) {
//printf("to match: hour = %d, minute = %d\n", cron_specs[i].hour, cron_specs[i].minute);
//printf("current: hour = %d, minute = %d\n", timeinfo.tm_hour, timeinfo.tm_min);
if (timeinfo.tm_hour == cron_specs[i].hour && (timeinfo.tm_min == cron_specs[i].minute)) {
if (cron_specs[i].last_ran_day == timeinfo.tm_mday) {
// it already ran today, skip it
continue;
}
printf("running, hour = %d, minute = %d\n", cron_specs[i].hour, cron_specs[i].minute);
// refactor this bit...
struct event_t message = {0};
switch (cron_specs[i].pump_num) {
case 0:
message.pump_1 = PUMP_ON;
message.pump_2 = PUMP_OFF;
break;
case 1:
message.pump_2 = PUMP_ON;
message.pump_1 = PUMP_OFF;
break;
}
message.pump_delay = make_delay(cron_specs[i].pump_on_time);
xQueueSend(pumpEventsHandle, (void*)&message, (TickType_t)0);
cron_specs[i].last_ran_day = timeinfo.tm_mday;
cron_specs[i].last_ran_hour = timeinfo.tm_hour;
cron_specs[i].last_ran_minute = timeinfo.tm_min;
}
}
}
esp_err_t
get_sensor_data(httpd_req_t *req) {
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
float temperature;
float humidity;
char resp[HTTPD_RESP_SIZE] = {0};
cJSON *resp_object_j = cJSON_CreateObject();
if (sht3x_measure(sensor, &temperature, &humidity)) {
cJSON_AddNumberToObject(resp_object_j, "temperature", (double)temperature);
cJSON_AddNumberToObject(resp_object_j, "humidity", (double)humidity);
}
cJSON_AddNumberToObject(resp_object_j, "hour", (double)timeinfo.tm_hour);
cJSON_AddNumberToObject(resp_object_j, "minute", (double)timeinfo.tm_min);
cJSON_PrintPreallocated(resp_object_j, resp, HTTPD_RESP_SIZE, false);
if (resp_object_j != NULL) { cJSON_Delete(resp_object_j); }
httpd_resp_set_type(req, "application/json");
httpd_resp_set_status(req, HTTPD_200);
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
esp_err_t
pumps_on_handler(httpd_req_t *req) {
printf("pumps_on_handler executed\n");
// TODO stream
char req_body[HTTPD_RESP_SIZE+1] = {0};
char resp[HTTPD_RESP_SIZE] = {0};
size_t body_size = MIN(req->content_len, (sizeof(req_body)-1));
// Receive body and do error handling
int ret = httpd_req_recv(req, req_body, body_size);
// if ret == 0 then no data
if (ret < 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
cJSON *json = cJSON_ParseWithLength(req_body, HTTPD_RESP_SIZE);
cJSON *pump_one_time_j = NULL;
cJSON *pump_two_time_j = NULL;
if (json != NULL) {
printf("%s\n", cJSON_Print(json));
if (cJSON_IsObject(json)) {
pump_one_time_j = cJSON_GetObjectItemCaseSensitive(json, "pump_1");
pump_two_time_j = cJSON_GetObjectItemCaseSensitive(json, "pump_2");
if (cJSON_IsNumber(pump_one_time_j) && cJSON_IsNumber(pump_two_time_j)) {
if (pump_one_time_j->valueint > 0 && pump_two_time_j->valueint > 0) {
printf("Running pumps: p1 = %d, p2 = %d\n", pump_one_time_j->valueint, pump_two_time_j->valueint);
runPumps(pump_one_time_j->valueint, pump_two_time_j->valueint);
}
}
}
}
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
if (json != NULL) { cJSON_Delete(json); }
return ESP_OK;
}
/* URI handler structure for GET /uri */
httpd_uri_t uri_get = {
.uri = "/sensor",
.method = HTTP_GET,
.handler = get_sensor_data,
.user_ctx = NULL
};
/* URI handler structure for POST /pumps_on */
httpd_uri_t pumps_on = {
.uri = "/pumps_on",
.method = HTTP_POST,
.handler = pumps_on_handler,
.user_ctx = NULL
};
esp_err_t
add_cron_handler(httpd_req_t *req) {
printf("add_cron_handler executed\n");
// TODO stream
char req_body[HTTPD_RESP_SIZE+1] = {0};
char resp[HTTPD_RESP_SIZE] = {0};
size_t body_size = MIN(req->content_len, (sizeof(req_body)-1));
// Receive body and do error handling
int ret = httpd_req_recv(req, req_body, body_size);
// if ret == 0 then no data
if (ret < 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
cJSON *json = cJSON_ParseWithLength(req_body, HTTPD_RESP_SIZE);
cJSON *pump_num;
cJSON *state;
cJSON *pump_on_time; // time it will be on for
cJSON *hour; // hour to trigger in
cJSON *minute; // minute on the hour to trigger on
struct cron_t cron_spec = {0};
if (json != NULL) {
printf("%s\n", cJSON_Print(json));
if (cJSON_IsObject(json)) {
pump_num = cJSON_GetObjectItemCaseSensitive(json, "pump_num");
state = cJSON_GetObjectItemCaseSensitive(json, "state");
pump_on_time = cJSON_GetObjectItemCaseSensitive(json, "pump_on_time");
hour = cJSON_GetObjectItemCaseSensitive(json, "hour");
minute = cJSON_GetObjectItemCaseSensitive(json, "minute");
if (cJSON_IsNumber(pump_num) &&
cJSON_IsNumber(state) &&
cJSON_IsNumber(pump_on_time) &&
cJSON_IsNumber(hour) &&
cJSON_IsNumber(minute)) {
printf("Creating cron spec\n");
cron_spec.pump_num = pump_num->valueint;
cron_spec.state = state->valueint;
cron_spec.pump_on_time = pump_on_time->valueint;
cron_spec.hour = hour->valueint;
cron_spec.minute = minute->valueint;
cron_spec.last_ran_day = -1;
cron_spec.last_ran_hour = -1;
cron_spec.last_ran_minute = -1;
printf("Parsed: hour = %d, minute = %d\n", cron_spec.hour, cron_spec.minute);
xQueueSend(timerEventsHandle, (void*)&cron_spec, (TickType_t)0);
}
}
}
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
if (json != NULL) { cJSON_Delete(json); }
return ESP_OK;
}
/* URI handler structure for POST /add_cron */
httpd_uri_t add_cron = {
.uri = "/add_cron",
.method = HTTP_POST,
.handler = add_cron_handler,
.user_ctx = NULL
};
/* Function for starting the webserver */
httpd_handle_t
start_webserver(void) {
/* Generate default configuration */
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
/* Empty handle to esp_http_server */
httpd_handle_t server = NULL;
/* Start the httpd server */
if (httpd_start(&server, &config) == ESP_OK) {
/* Register URI handlers */
httpd_register_uri_handler(server, &uri_get);
httpd_register_uri_handler(server, &pumps_on);
httpd_register_uri_handler(server, &add_cron);
}
/* If server failed to start, handle will be NULL */
ESP_LOGI(TAG, "webserver started");
return server;
}
static SemaphoreHandle_t s_semph_get_ip_addrs;
/* tear down connection, release resources */
static void
stop(void) {
#if CONFIG_EXAMPLE_CONNECT_WIFI
wifi_stop();
s_active_interfaces--;
#endif
}
esp_err_t
wifi_disconnect(void) {
if (s_semph_get_ip_addrs == NULL) {
return ESP_ERR_INVALID_STATE;
}
vSemaphoreDelete(s_semph_get_ip_addrs);
s_semph_get_ip_addrs = NULL;
stop();
ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&stop));
return ESP_OK;
}
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 48
#endif
/* Variable holding number of times ESP32 restarted since first boot.
* It is placed into RTC memory using RTC_DATA_ATTR and
* maintains its value when ESP32 wakes from deep sleep.
*/
RTC_DATA_ATTR static int boot_count = 0;
static void obtain_time(void);
static void initialize_sntp(void);
void wifi_init_sta(void);
void
time_sync_notification_cb(struct timeval *tv) {
ESP_LOGI(TAG, "Notification of a time synchronization event");
}
static void
obtain_time(void) {
/**
* NTP server address could be aquired via DHCP,
* see following menuconfig options:
* 'LWIP_DHCP_GET_NTP_SRV' - enable STNP over DHCP
* 'LWIP_SNTP_DEBUG' - enable debugging messages
*
*/
wifi_init_sta();
initialize_sntp();
// wait for time to be set
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 15;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
time(&now);
localtime_r(&now, &timeinfo);\
}
static void
initialize_sntp(void) {
ESP_LOGI(TAG, "Initializing SNTP");
sntp_setoperatingmode(SNTP_OPMODE_POLL);
/*
* If 'NTP over DHCP' is enabled, we set dynamic pool address
* as a 'secondary' server. It will act as a fallback server in case that address
* provided via NTP over DHCP is not accessible
*/
#if LWIP_DHCP_GET_NTP_SRV && SNTP_MAX_SERVERS > 1
sntp_setservername(1, "pool.ntp.org");
#if LWIP_IPV6 && SNTP_MAX_SERVERS > 2 // statically assigned IPv6 address is also possible
ip_addr_t ip6;
if (ipaddr_aton("2a01:3f7::1", &ip6)) { // ipv6 ntp source "ntp.netnod.se"
sntp_setserver(2, &ip6);
}
#endif /* LWIP_IPV6 */
#else /* LWIP_DHCP_GET_NTP_SRV && (SNTP_MAX_SERVERS > 1) */
// otherwise, use DNS address from a pool
sntp_setservername(0, "pool.ntp.org");
#endif
sntp_set_time_sync_notification_cb(time_sync_notification_cb);
sntp_init();
ESP_LOGI(TAG, "List of configured NTP servers:");
for (uint8_t i = 0; i < SNTP_MAX_SERVERS; ++i){
if (sntp_getservername(i)){
ESP_LOGI(TAG, "server %d: %s", i, sntp_getservername(i));
} else {
// we have either IPv4 or IPv6 address, let's print it
char buff[INET6_ADDRSTRLEN];
ip_addr_t const *ip = sntp_getserver(i);
if (ipaddr_ntoa_r(ip, buff, INET6_ADDRSTRLEN) != NULL)
ESP_LOGI(TAG, "server %d: %s", i, buff);
}
}
}
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
static void
event_handler(void *arg,
esp_event_base_t event_base,
int32_t event_id,
void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
}
else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void
wifi_init_sta(void) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
#ifdef LWIP_DHCP_GET_NTP_SRV
sntp_servermode_dhcp(1); // accept NTP offers from DHCP server, if any
#endif
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* Setting a password implies station will connect to all security modes including WEP/WPA.
* However these modes are deprecated and not advisable to be used. Incase your Access point
* doesn't support WPA2, these mode can be enabled by commenting below line */
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
httpd_handle_t server;
printf("Trying to start webserver\n");
server = start_webserver();
}
else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}
else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
static void
ledc_init(int gpio_num,
int ledc_channel_num,
int ledc_timer_num) {
// Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE,
.timer_num = ledc_timer_num,
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = LEDC_FREQUENCY, // Set output frequency at 5 kHz
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_MODE,
.channel = ledc_channel_num,
.timer_sel = ledc_timer_num,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = gpio_num,
.duty = 0, // Set duty to 0%
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}
void user_task (void *pvParameters)
{
float temperature;
float humidity;
TickType_t last_wakeup = xTaskGetTickCount();
while (1)
{
// perform one measurement and do something with the results
if (sht3x_measure (sensor, &temperature, &humidity))
printf("%.3f SHT3x Sensor: %.2f °C, %.2f %%\n",
(double)sdk_system_get_time()*1e-3, temperature, humidity);
// wait until 5 seconds are over
vTaskDelayUntil(&last_wakeup, 5000 / portTICK_PERIOD_MS);
}
}
/* -- main program ------------------------------------------------- */
void user_init(void)
{
// Set UART Parameter.
uart_set_baud(0, 115200);
// Give the UART some time to settle
vTaskDelay(1);
// Init I2C bus interfaces at which SHT3x sensors are connected
// (different busses are possible).
i2c_init(I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ_100K);
// Create the sensors, multiple sensors are possible.
if ((sensor = sht3x_init_sensor (I2C_BUS, SHT3x_ADDR_1)))
{
// Create a user task that uses the sensors.
xTaskCreate(user_task, "user_task", TASK_STACK_SIZE, NULL, 2, 0);
}
// That's it.
}
void
app_main(void) {
++boot_count;
ESP_LOGI(TAG, "Boot count: %d", boot_count);
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
// Is time set? If not, tm_year will be (1970 - 1900).
if (timeinfo.tm_year < (2016 - 1900)) {
ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
obtain_time();
// update 'now' variable with current time
time(&now);
}
char strftime_buf[64];
// Set timezone to Eastern Standard Time and print local time
setenv("TZ", "EST5EDT,M3.2.0/2,M11.1.0", 1);
tzset();
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in New York is: %s", strftime_buf);
if (sntp_get_sync_mode() == SNTP_SYNC_MODE_SMOOTH) {
struct timeval outdelta;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_IN_PROGRESS) {
adjtime(NULL, &outdelta);
ESP_LOGI(TAG, "Waiting for adjusting time ... outdelta = %jd sec: %li ms: %li us",
(intmax_t)outdelta.tv_sec,
outdelta.tv_usec/1000,
outdelta.tv_usec%1000);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
pumpEventsHandle = xQueueCreateStatic(PUMP_EV_NUM, sizeof (struct event_t), queueStorage, &pumpEvents);
timerEventsHandle = xQueueCreateStatic(PUMP_EV_NUM, sizeof (struct cron_t), timerQueueStorage, &timerEvents);
configASSERT(pumpEventsHandle);
configASSERT(timerEventsHandle);
pumpTimer = xTimerCreateStatic("pump timer", PUMP_CB_PERIOD, pdTRUE, (void*)0, pumpTimerCb, &pumpTimerBuffer);
xTimerStart(pumpTimer, 0);
// Pump stuff
// Set the LEDC peripheral configuration
ledc_init(18, 0, 0);
ledc_init(19, 1, 1);
i2c_init(I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ_100K);
// Create the sensors, multiple sensors are possible.
sensor = sht3x_init_sensor(I2C_BUS, SHT3x_ADDR_1);
createPumpRunnerTask();
}

61
main/plant_water.h

@ -0,0 +1,61 @@
#include "driver/ledc.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_eth.h"
#include "esp_event.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_sleep.h"
#include "esp_sntp.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "sdkconfig.h"
#include <esp_event.h>
#include <esp_http_server.h>
#include <esp_log.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <nvs_flash.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/time.h>
#include <time.h>
#include "driver/spi_common.h"
#include "driver/uart.h"
#define LEDC_TIMER_0 0
#define LEDC_TIMER_1 1
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_OUTPUT_IO_1 (18) // Define the output GPIO
#define LEDC_OUTPUT_IO_2 (19) // Define the output GPIO
#define LEDC_CHANNEL_1 0
#define LEDC_CHANNEL_2 1
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // Set duty resolution to 13 bits
#define LEDC_DUTY (8191) // Set duty to 100%. ((2 ** 13) - 1) * 50% = 4095
#define LEDC_FREQUENCY (5000) // Frequency in Hertz. Set frequency at 5 kHz
#define EXAMPLE_ESP_WIFI_SSID CONFIG_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY 10
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#define PUMP_EV_NUM 5
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
#define TASK_STACK_SIZE 4000

93
main/protocol_examples_common.h

@ -0,0 +1,93 @@
/* Common functions for protocol examples, to establish Wi-Fi or Ethernet connection.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
#include "esp_netif.h"
#include "esp_eth.h"
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
#define EXAMPLE_INTERFACE get_example_netif()
#endif
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
#define EXAMPLE_INTERFACE get_example_netif()
#endif
#if !defined (CONFIG_EXAMPLE_CONNECT_ETHERNET) && !defined (CONFIG_EXAMPLE_CONNECT_WIFI)
// This is useful for some tests which do not need a network connection
#define EXAMPLE_INTERFACE NULL
#endif
/**
* @brief Configure Wi-Fi or Ethernet, connect, wait for IP
*
* This all-in-one helper function is used in protocols examples to
* reduce the amount of boilerplate in the example.
*
* It is not intended to be used in real world applications.
* See examples under examples/wifi/getting_started/ and examples/ethernet/
* for more complete Wi-Fi or Ethernet initialization code.
*
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*
* @return ESP_OK on successful connection
*/
esp_err_t example_connect(void);
/**
* Counterpart to example_connect, de-initializes Wi-Fi or Ethernet
*/
esp_err_t example_disconnect(void);
/**
* @brief Configure stdin and stdout to use blocking I/O
*
* This helper function is used in ASIO examples. It wraps installing the
* UART driver and configuring VFS layer to use UART driver for console I/O.
*/
esp_err_t example_configure_stdin_stdout(void);
/**
* @brief Returns esp-netif pointer created by example_connect()
*
* @note If multiple interfaces active at once, this API return NULL
* In that case the get_example_netif_from_desc() should be used
* to get esp-netif pointer based on interface description
*/
esp_netif_t *get_example_netif(void);
/**
* @brief Returns esp-netif pointer created by example_connect() described by
* the supplied desc field
*
* @param desc Textual interface of created network interface, for example "sta"
* indicate default WiFi station, "eth" default Ethernet interface.
*
*/
esp_netif_t *get_example_netif_from_desc(const char *desc);
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
/**
* @brief Get the example Ethernet driver handle
*
* @return esp_eth_handle_t
*/
esp_eth_handle_t get_example_eth_handle(void);
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
#ifdef __cplusplus
}
#endif
Loading…
Cancel
Save