Browse Source

initial commit

main
Nicolas Massé 4 years ago
commit
33ae8b7294
  1. 7
      .gitignore
  2. 8
      .gitmodules
  3. 8
      CMakeLists.txt
  4. 8
      Makefile
  5. 2
      README.md
  6. 1
      components/esp32-ds18b20
  7. 1
      components/esp32-owb
  8. 2
      main/CMakeLists.txt
  9. 111
      main/Kconfig.projbuild
  10. 27
      main/cacert.pem
  11. 20
      main/common.c
  12. 20
      main/common.h
  13. 4
      main/component.mk
  14. 48
      main/main.c
  15. 242
      main/mqtt.c
  16. 22
      main/mqtt.h
  17. 94
      main/ota.c
  18. 6
      main/ota.h
  19. 38
      main/sntp.c
  20. 6
      main/sntp.h
  21. 299
      main/solar.c
  22. 7
      main/solar.h
  23. 61
      main/wifi.c
  24. 6
      main/wifi.h
  25. 5
      provision/.gitignore
  26. 6
      provision/CMakeLists.txt
  27. 8
      provision/Makefile
  28. 2
      provision/README.md
  29. 2
      provision/main/CMakeLists.txt
  30. 68
      provision/main/Kconfig.projbuild
  31. 8
      provision/main/component.mk
  32. 169
      provision/main/provision.c
  33. 1
      sdkconfig.defaults
  34. 19
      sdkconfig.dev
  35. 9
      sdkconfig.prod

7
.gitignore

@ -0,0 +1,7 @@
build
sdkconfig
sdkconfig.old
# Used during development to test OTA
# See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system.html
version.txt

8
.gitmodules

@ -0,0 +1,8 @@
[submodule "esp32-owb"]
path = components/esp32-owb
url = https://github.com/DavidAntliff/esp32-owb.git
branch = master
[submodule "esp32-ds18b20"]
path = components/esp32-ds18b20
url = https://github.com/DavidAntliff/esp32-ds18b20.git
branch = master

8
CMakeLists.txt

@ -0,0 +1,8 @@
# 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(solar_controller)
target_add_binary_data(solar_controller.elf "main/cacert.pem" TEXT)

8
Makefile

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

2
README.md

@ -0,0 +1,2 @@
# Solar Controller

1
components/esp32-ds18b20

@ -0,0 +1 @@
Subproject commit d677f09a42898a7c3bd064cf8afd89f9d4d6ada9

1
components/esp32-owb

@ -0,0 +1 @@
Subproject commit 60d977e7031f3fcb3900bce4f88befb159bfef4f

2
main/CMakeLists.txt

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c" "solar.c" "wifi.c" "mqtt.c" "sntp.c" "common.c" "ota.c"
INCLUDE_DIRS ".")

111
main/Kconfig.projbuild

@ -0,0 +1,111 @@
menu "Solar controller configuration"
config SC_1WIRE_GPIO
int "GPIO number for the 1-wire bus"
range 0 34 if IDF_TARGET_ESP32
range 0 46 if IDF_TARGET_ESP32S2
range 0 19 if IDF_TARGET_ESP32C3
default 4
help
GPIO number for the 1-wire bus.
config SC_SAMPLE_PERIOD
int "Number of seconds between readings."
range 1 3600
default 1
help
Number of seconds between readings.
config SC_PANEL_PUMP_GPIO
int "PIN used to send PWM signal to the panel pump."
range 0 34 if IDF_TARGET_ESP32
range 0 46 if IDF_TARGET_ESP32S2
range 0 19 if IDF_TARGET_ESP32C3
default 19
help
GPIO number for the panel pump.
config SC_FLOOR_HEATING_PUMP_GPIO
int "PIN used to send PWM signal to the floor heating pump."
range 0 34 if IDF_TARGET_ESP32
range 0 46 if IDF_TARGET_ESP32S2
range 0 19 if IDF_TARGET_ESP32C3
default 21
help
GPIO number for the floor heating pump.
config SC_PUMP_PWM_FREQ
int "PWM Frequency used to drive both pumps."
range 1000 10000
default 1000
help
PWM Frequency used to drive both pumps.
config SC_STACK_SIZE
int "Stack size for the solar controller."
range 1024 16384
default 4096
help
Defines stack size. Insufficient stack size can cause crash.
config SC_FROST_PROTECTION_TEMP_LOW
int "Frost protection temperature (lower bound)."
range -30 100
default -4
help
If the temperature is lower, starts the panel pump.
config SC_FROST_PROTECTION_TEMP_HIGH
int "Frost protection temperature (higher bound)."
range -30 100
default 1
help
If the temperature is higher, stops the panel pump.
config SC_PANEL_DELTA_TEMP_LOW
int "Difference of temperature at which the panel pump stops (lower bound)."
range -30 100
default 5
help
If the temperature is lower, starts the panel pump.
config SC_PANEL_DELTA_TEMP_HIGH
int "Difference of temperature at which the panel pump starts (higher bound)."
range -30 100
default 10
help
If the temperature is higher, stops the panel pump.
config SC_FLOOR_HEATING_DELTA_TEMP_LOW
int "Difference of temperature at which the floor heating pump stops (lower bound)."
range -30 100
default 5
help
If the temperature is lower, starts the panel pump.
config SC_FLOOR_HEATING_DELTA_TEMP_HIGH
int "Difference of temperature at which the floor heating pump starts (higher bound)."
range -30 100
default 7
help
If the temperature is higher, stops the panel pump.
config MQTT_LWT_TOPIC
string "MQTT Last Will Testament Topic"
default "solar-controller/connected"
help
Topic where to send LWT (Last Will Testament) upon disconnect.
config MQTT_COMMAND_TOPIC
string "MQTT system command Topic"
default "solar-controller/command"
help
Topic where to send system commands.
config MQTT_SOLAR_VALUE_TOPIC
string "Where to send measures"
default "solar-controller/status/solar/%s"
help
Topic where to measures.
endmenu

27
main/cacert.pem

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE
BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD
EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG
EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT
DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r
Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1
3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K
b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN
Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ
4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf
1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu
hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH
usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r
OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY
9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV
0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt
hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw
TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx
e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA
JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD
YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n
JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ
m+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

20
main/common.c

@ -0,0 +1,20 @@
#include "common.h"
volatile EventGroupHandle_t services_event_group;
char* get_nvs_string(nvs_handle_t nvs, char* key) {
size_t required_size;
esp_err_t err = nvs_get_str(nvs, key, NULL, &required_size);
if (err != ESP_OK) {
return NULL;
}
char* value = malloc(required_size);
if (!value) {
return NULL;
}
err = nvs_get_str(nvs, key, value, &required_size);
if (err != ESP_OK) {
free(value);
return NULL;
}
return value;
}

20
main/common.h

@ -0,0 +1,20 @@
#ifndef __COMMON_H__
#define __COMMON_H__
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "nvs_flash.h"
#include "nvs.h"
#define WIFI_CONNECTED_BIT BIT0
#define MQTT_CONNECTED_BIT BIT1
#define TIME_SYNC_BIT BIT2
extern volatile EventGroupHandle_t services_event_group;
char* get_nvs_string(nvs_handle_t nvs, char* key);
#define WAIT_FOR(flags) while ((xEventGroupWaitBits(services_event_group, flags, pdFALSE, pdTRUE, portMAX_DELAY) & (flags)) != (flags)) {}
#define IS_READY(flags) ((xEventGroupGetBits(services_event_group) & (flags)) == (flags))
#endif

4
main/component.mk

@ -0,0 +1,4 @@
#
# Main Makefile. This is basically the same as a component makefile.
#
COMPONENT_EMBED_TXTFILES := cacert.pem

48
main/main.c

@ -0,0 +1,48 @@
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_tls.h"
#include "esp_crt_bundle.h"
#include "wifi.h"
#include "mqtt.h"
#include "solar.h"
#include "sntp.h"
#include "common.h"
#include "esp_ota_ops.h"
#include "ota.h"
// Embedded via component.mk
extern const uint8_t cacert_pem_start[] asm("_binary_cacert_pem_start");
extern const uint8_t cacert_pem_end[] asm("_binary_cacert_pem_end");
void app_main(void) {
const esp_app_desc_t* current = esp_ota_get_app_description();
ESP_LOGI("main", "Currently running %s version %s", current->project_name, current->version);
// NVS is used to store wifi credentials. So, we need to initialize it first.
ESP_ERROR_CHECK(nvs_flash_init());
// Inject the Let's Encrypt Root CA certificate in the global CA store
ESP_ERROR_CHECK(esp_tls_init_global_ca_store());
ESP_ERROR_CHECK(esp_tls_set_global_ca_store((const unsigned char *) cacert_pem_start, cacert_pem_end - cacert_pem_start));
// Create an event group to keep track of service readiness
services_event_group = xEventGroupCreate();
// Start the solar controller early because we need it working even in case
// of missing wifi network.
solar_init();
// Then connect to Wifi, MQTT and NTP
wifi_init_sta();
WAIT_FOR(WIFI_CONNECTED_BIT);
mqtt_init();
sntp_start();
WAIT_FOR(MQTT_CONNECTED_BIT|TIME_SYNC_BIT);
}

242
main/mqtt.c

@ -0,0 +1,242 @@
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "esp_tls.h"
#include "esp_ota_ops.h"
#include <sys/param.h>
#include <time.h>
#include "cJSON.h"
#include "common.h"
#include "ota.h"
#include "mqtt.h"
#include "solar.h"
static esp_mqtt_client_config_t mqtt_cfg;
static esp_mqtt_client_handle_t client;
static const char *MQTT_LOGGER = "mqtt";
#define MQTT_QOS_0 0
#define MQTT_QOS_1 1
#define MQTT_QOS_2 2
#define MQTT_NO_RETAIN 0
#define MQTT_RETAIN 1
#define JSON_BUFFER_SIZE 128
#define MQTT_TOPIC_BUFFER_SIZE 128
#define SYSTEM_COMMAND_UPDATE "firmware-update"
#define SYSTEM_COMMAND_REBOOT "reboot"
#define SYSTEM_COMMAND_SET_PARAM "set-parameter"
#define PARAMETER_HEATING_ENABLED "heating_enabled"
void mqtt_publish_data(char* key, json_value jv) {
char topic[MQTT_TOPIC_BUFFER_SIZE];
char payload[JSON_BUFFER_SIZE];
time_t now;
int retain = MQTT_RETAIN;
int qos = MQTT_QOS_1;
// Format the MQTT topic
if (!snprintf(topic, MQTT_TOPIC_BUFFER_SIZE, CONFIG_MQTT_SOLAR_VALUE_TOPIC, key)) {
ESP_LOGD(MQTT_LOGGER, "mqtt_publish_data: snprintf failed!");
return;
}
cJSON *root = cJSON_CreateObject();
if (root == NULL) {
ESP_LOGD(MQTT_LOGGER, "mqtt_publish_data: cJSON_CreateObject failed!");
return;
}
// Add the value
if (jv.type == MQTT_TYPE_STRING) {
cJSON_AddStringToObject(root, "val", (char*)jv.value.str);
} else if (jv.type == MQTT_TYPE_FLOAT) {
float f = jv.value.f;
cJSON_AddNumberToObject(root, "val", (double)f);
} else if (jv.type == MQTT_TYPE_INT) {
int i = jv.value.i;
cJSON_AddNumberToObject(root, "val", (double)i);
}
// Add a timestamp
time(&now);
cJSON_AddNumberToObject(root, "ts", (double)now);
if (!cJSON_PrintPreallocated(root, payload, JSON_BUFFER_SIZE, 0)) {
ESP_LOGD(MQTT_LOGGER, "mqtt_publish_data: cJSON_PrintPreallocated failed!");
cJSON_Delete(root);
return;
}
cJSON_Delete(root);
if (esp_mqtt_client_publish(client, topic, payload, 0, qos, retain) == -1) {
ESP_LOGD(MQTT_LOGGER, "MQTT Message discarded!");
}
}
esp_err_t mqtt_process_system_command(char* data, int data_len) {
esp_err_t status = ESP_OK;
cJSON *json = cJSON_ParseWithLength(data, data_len);
if (json == NULL) {
ESP_LOGI(MQTT_LOGGER, "Error parsing MQTT system command as JSON");
status = ESP_FAIL;
goto end;
}
cJSON* command = cJSON_GetObjectItemCaseSensitive(json, "command");
if (!cJSON_IsString(command) || (command->valuestring == NULL)) {
ESP_LOGD(MQTT_LOGGER, "Expected a command name!");
status = ESP_FAIL;
goto end;
}
if (strcmp(command->valuestring, SYSTEM_COMMAND_REBOOT) == 0) {
ESP_LOGE(MQTT_LOGGER, "Received a reboot command. Rebooting now!");
esp_restart();
goto end;
}
if (strcmp(command->valuestring, SYSTEM_COMMAND_UPDATE) == 0) {
cJSON* args = cJSON_GetObjectItemCaseSensitive(json, "args");
if (!cJSON_IsObject(args)) {
ESP_LOGD(MQTT_LOGGER, "Expected a command argument!");
status = ESP_FAIL;
goto end;
}
cJSON* version = cJSON_GetObjectItemCaseSensitive(args, "version");
if (!cJSON_IsString(version) || (version->valuestring == NULL)) {
ESP_LOGD(MQTT_LOGGER, "Expected a version numer!");
status = ESP_FAIL;
goto end;
}
trigger_ota_update(version->valuestring);
goto end;
} else if (strcmp(command->valuestring, SYSTEM_COMMAND_SET_PARAM) == 0) {
cJSON* args = cJSON_GetObjectItemCaseSensitive(json, "args");
if (!cJSON_IsObject(args)) {
ESP_LOGD(MQTT_LOGGER, "Expected a command argument!");
status = ESP_FAIL;
goto end;
}
cJSON* name = cJSON_GetObjectItemCaseSensitive(args, "name");
if (!cJSON_IsString(name) || (name->valuestring == NULL)) {
ESP_LOGD(MQTT_LOGGER, "Expected a parameter name!");
status = ESP_FAIL;
goto end;
}
char * param_name = name->valuestring;
if (strcmp(param_name, PARAMETER_HEATING_ENABLED) == 0) {
cJSON* value = cJSON_GetObjectItemCaseSensitive(args, "value");
if (!cJSON_IsNumber(value)) {
ESP_LOGD(MQTT_LOGGER, "Expected a parameter value with type 'number'!");
status = ESP_FAIL;
goto end;
}
int heating_enabled = value->valuedouble;
solar_set_heating(heating_enabled);
}
goto end;
}
ESP_LOGW(MQTT_LOGGER, "Unknown system command %s!", command->valuestring);
status = ESP_FAIL;
end:
cJSON_Delete(json);
return status;
}
esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) {
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(MQTT_LOGGER, "MQTT_EVENT_CONNECTED");
xEventGroupSetBits(services_event_group, MQTT_CONNECTED_BIT);
if (esp_mqtt_client_publish(client, CONFIG_MQTT_LWT_TOPIC, "1", 0, MQTT_QOS_0, MQTT_RETAIN) == -1) {
ESP_LOGD(MQTT_LOGGER, "MQTT Message discarded!");
}
if (esp_mqtt_client_subscribe(client, CONFIG_MQTT_COMMAND_TOPIC, MQTT_QOS_1) == -1) {
ESP_LOGD(MQTT_LOGGER, "Could not subscribe to " CONFIG_MQTT_COMMAND_TOPIC " MQTT topic");
}
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(MQTT_LOGGER, "MQTT_EVENT_DISCONNECTED");
xEventGroupClearBits(services_event_group, MQTT_CONNECTED_BIT);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGD(MQTT_LOGGER, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(MQTT_LOGGER, "MQTT_EVENT_ERROR");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGI(MQTT_LOGGER, "Last error code reported from esp-tls: 0x%x", event->error_handle->esp_tls_last_esp_err);
ESP_LOGI(MQTT_LOGGER, "Last tls stack error number: 0x%x", event->error_handle->esp_tls_stack_err);
ESP_LOGI(MQTT_LOGGER, "Last captured errno : %d (%s)", event->error_handle->esp_transport_sock_errno,
strerror(event->error_handle->esp_transport_sock_errno));
} else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
ESP_LOGI(MQTT_LOGGER, "Connection refused error: 0x%x", event->error_handle->connect_return_code);
} else {
ESP_LOGW(MQTT_LOGGER, "Unknown error type: 0x%x", event->error_handle->error_type);
}
break;
case MQTT_EVENT_DATA:
if (strncmp(event->topic, CONFIG_MQTT_COMMAND_TOPIC, event->topic_len) == 0) {
ESP_LOGD(MQTT_LOGGER, "Received an MQTT system command!");
mqtt_process_system_command(event->data, event->data_len);
}
break;
case MQTT_EVENT_SUBSCRIBED:
// Expected event. Nothing to do.
break;
default:
ESP_LOGD(MQTT_LOGGER, "Other event id:%d", event->event_id);
break;
}
return ESP_OK;
}
void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
ESP_LOGD(MQTT_LOGGER, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
mqtt_event_handler_cb(event_data);
}
void mqtt_init(void) {
nvs_handle_t nvs;
esp_err_t err = nvs_open("mqtt", NVS_READONLY, &nvs);
if (err != ESP_OK) {
ESP_LOGE(MQTT_LOGGER, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
return;
}
memset(&mqtt_cfg, 0, sizeof(mqtt_cfg));
mqtt_cfg.uri = get_nvs_string(nvs, "url");
mqtt_cfg.use_global_ca_store = true;
mqtt_cfg.username = get_nvs_string(nvs, "username");
mqtt_cfg.password = get_nvs_string(nvs, "password");
mqtt_cfg.lwt_topic = CONFIG_MQTT_LWT_TOPIC;
mqtt_cfg.lwt_msg = "0";
mqtt_cfg.lwt_qos = MQTT_QOS_0;
mqtt_cfg.lwt_retain = MQTT_RETAIN;
nvs_close(nvs);
client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
esp_mqtt_client_start(client);
}

22
main/mqtt.h

@ -0,0 +1,22 @@
#ifndef __MQTT_H__
#define __MQTT_H__
#define MQTT_TYPE_UNDEFINED 0
#define MQTT_TYPE_STRING 1
#define MQTT_TYPE_INT 2
#define MQTT_TYPE_FLOAT 3
typedef struct {
int type;
union {
char* str;
float f;
int i;
} value;
} json_value;
void mqtt_init();
void mqtt_publish_data(char* key, json_value value);
#endif

94
main/ota.c

@ -0,0 +1,94 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "esp_https_ota.h"
#include "esp_ota_ops.h"
#include "ota.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "common.h"
static const char *OTA_LOGGER = "ota_update";
// Embedded via component.mk
extern const uint8_t cacert_pem_start[] asm("_binary_cacert_pem_start");
extern const uint8_t cacert_pem_end[] asm("_binary_cacert_pem_end");
esp_err_t do_firmware_upgrade(char* firmware_url) {
esp_http_client_config_t config = {
.url = firmware_url,
.cert_pem = (char *)cacert_pem_start,
};
esp_https_ota_config_t ota_config = {
// Can be enabled in an upcoming version of the Espressif SDK
// to save some memory.
// Requires CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=4096 in sdkconfig.
//
//.partial_http_download = true,
//.max_http_request_size = 4096,
.http_config = &config,
};
esp_https_ota_handle_t https_ota_handle = NULL;
esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
if (https_ota_handle == NULL) {
return ESP_FAIL;
}
while (1) {
err = esp_https_ota_perform(https_ota_handle);
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
break;
}
}
if (err != ESP_OK) {
esp_https_ota_abort(https_ota_handle);
return err;
}
esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle);
if (ota_finish_err != ESP_OK) {
return ota_finish_err;
}
esp_restart(); // this function never returns
return ESP_OK;
}
void trigger_ota_update(char* version) {
const esp_app_desc_t* current = esp_ota_get_app_description();
ESP_LOGD(OTA_LOGGER, "Currently running %s version %s", current->project_name, current->version);
if (strcmp(version, current->version) == 0) {
// already to latest version
return;
}
nvs_handle_t nvs;
esp_err_t err = nvs_open("ota", NVS_READONLY, &nvs);
if (err != ESP_OK) {
ESP_LOGE(OTA_LOGGER, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
return;
}
char* update_url_pattern = get_nvs_string(nvs, "update_url");
nvs_close(nvs);
if (update_url_pattern == NULL) {
return;
}
const size_t buffer_size = 256;
char update_url[buffer_size];
// Format the update URL
if (!snprintf(update_url, buffer_size, update_url_pattern, version)) {
ESP_LOGD(OTA_LOGGER, "trigger_ota_update: snprintf failed!");
free(update_url_pattern);
return;
}
esp_err_t ret = do_firmware_upgrade(update_url);
ESP_LOGW(OTA_LOGGER, "do_firmware_upgrade failed with error %d", ret);
}

6
main/ota.h

@ -0,0 +1,6 @@
#ifndef __OTA_H__
#define __OTA_H__
void trigger_ota_update(char* version);
#endif

38
main/sntp.c

@ -0,0 +1,38 @@
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_sntp.h"
#include "sntp.h"
#include "common.h"
static const char *SNTP_LOGGER = "sntp";
void sntp_callback(struct timeval *tv) {
if (sntp_get_sync_mode() == SNTP_SYNC_MODE_IMMED) {
time_t now;
time(&now);
struct tm now_tm;
localtime_r(&now, &now_tm);
char strftime_buf[64];
if (strftime(strftime_buf, sizeof(strftime_buf), "%c", &now_tm)) {
ESP_LOGI(SNTP_LOGGER, "Time synchronized: %s", strftime_buf);
}
sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH);
xEventGroupSetBits(services_event_group, TIME_SYNC_BIT);
}
}
void sntp_start() {
ESP_LOGI(SNTP_LOGGER, "Initializing SNTP...");
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "pool.ntp.org");
sntp_set_time_sync_notification_cb(sntp_callback);
sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED);
sntp_init();
}

6
main/sntp.h

@ -0,0 +1,6 @@
#ifndef __SNTP_H__
#define __SNTP_H__
void sntp_start();
#endif

299
main/solar.c

@ -0,0 +1,299 @@
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "solar.h"
#include "mqtt.h"
#include "common.h"
// 1-Wire support
#include "owb.h"
#include "owb_rmt.h"
#include "ds18b20.h"
// PWM support
#include "driver/gpio.h"
#include "driver/ledc.h"
#define SC_MAX_TEMPERATURE_SENSORS_COUNT 8
#define SC_EXPECTED_TEMPERATURE_SENSORS_COUNT 4
#define SC_PUMP_FULL_POWER 255
#define SC_PUMP_HALF_POWER 150
#define SC_PUMP_NO_POWER 0
#define SC_LEDC_CHANNEL_PANEL_PUMP LEDC_CHANNEL_0
#define SC_LEDC_CHANNEL_FLOOR_HEATING_PUMP LEDC_CHANNEL_1
static const char *SOLAR_LOGGER = "solar";
static DS18B20_Info temperature_sensors[SC_EXPECTED_TEMPERATURE_SENSORS_COUNT];
static int panel;
static int store_high;
static int store_low;
static int floor_heating;
static OneWireBus * owb;
static owb_rmt_driver_info rmt_driver_info;
static int is_heating_enabled = 0;
void solar_set_heating(int enabled) {
is_heating_enabled = enabled;
if (IS_READY(MQTT_CONNECTED_BIT|TIME_SYNC_BIT)) {
mqtt_publish_data("floor_heating_enabled", (json_value){MQTT_TYPE_INT, {.i = is_heating_enabled}});
}
}
static int map_pwm(float delta_temp) {
float res = delta_temp * 10.2;
if (res > 255) {
return 255;
}
return res;
}
static void solar_process(void *pvParameters) {
TickType_t last_wake_time = xTaskGetTickCount();
int is_protecting_from_frost = 0;
int is_loading = 0;
int is_heating = 0;
int panel_pump_pwm = 0;
int floor_heating_pump_pwm = 0;
int errors_count[SC_EXPECTED_TEMPERATURE_SENSORS_COUNT] = {0};
for (;;) {
float readings[SC_EXPECTED_TEMPERATURE_SENSORS_COUNT];
DS18B20_ERROR errors[SC_EXPECTED_TEMPERATURE_SENSORS_COUNT] = { 0 };
// Read temperatures more efficiently by starting conversions on all devices at the same time
ds18b20_convert_all(owb);
// In this application all devices use the same resolution,
// so use the first device to determine the delay
ds18b20_wait_for_conversion(&temperature_sensors[0]);
// Read the results immediately after conversion otherwise it may fail
// (using printf before reading may take too long)
for (int i = 0; i < SC_EXPECTED_TEMPERATURE_SENSORS_COUNT; ++i) {
errors[i] = ds18b20_read_temp(&temperature_sensors[i], &readings[i]);
}
// Check if there are errors during measure or data transmission
int has_reading_errors = 0;
for (int i = 0; i < SC_EXPECTED_TEMPERATURE_SENSORS_COUNT; ++i) {
if (errors[i] != DS18B20_OK) {
char rom_code_s[17];
owb_string_from_rom_code(temperature_sensors[i].rom_code, rom_code_s, sizeof(rom_code_s));
ESP_LOGW(SOLAR_LOGGER, "Error reading sensor %s.", rom_code_s);
++errors_count[i];
has_reading_errors = 1;
}
}
// And retry a measure if there were errors
if (has_reading_errors) {
ESP_LOGW(SOLAR_LOGGER, "There were errors while reading sensors. Retrying!");
continue;
}
float delta_panel = readings[panel] - readings[store_low];
float delta_floor_heating = readings[store_high] - readings[floor_heating];
if (!is_protecting_from_frost && readings[panel] < CONFIG_SC_FROST_PROTECTION_TEMP_LOW) {
ESP_LOGI(SOLAR_LOGGER, "Temperature is running low in the panel (%.1f), engaging frost protection!", readings[panel]);
is_protecting_from_frost = 1;
panel_pump_pwm = SC_PUMP_HALF_POWER;
} else if (is_protecting_from_frost && readings[panel] > CONFIG_SC_FROST_PROTECTION_TEMP_HIGH) {
ESP_LOGI(SOLAR_LOGGER, "Disengaging frost protection...");
is_protecting_from_frost = 0;
panel_pump_pwm = SC_PUMP_NO_POWER;
} else if (!is_loading && delta_panel > CONFIG_SC_PANEL_DELTA_TEMP_HIGH) {
ESP_LOGI(SOLAR_LOGGER, "Start loading heat from the panel to the store...");
panel_pump_pwm = map_pwm(delta_panel);
is_loading = 1;
} else if (is_loading && delta_panel < CONFIG_SC_PANEL_DELTA_TEMP_LOW) {
ESP_LOGI(SOLAR_LOGGER, "Stop loading heat from the panel to the store...");
panel_pump_pwm = SC_PUMP_NO_POWER;
is_loading = 0;
} else if (is_loading) {
// Keep adjusting the pump duty cycle according to the temperature difference
panel_pump_pwm = map_pwm(delta_panel);
}
if (is_heating && !is_heating_enabled) {
ESP_LOGI(SOLAR_LOGGER, "Stop heating the floor (as requested).");
floor_heating_pump_pwm = SC_PUMP_NO_POWER;
is_heating = 0;
} else if (is_heating_enabled && !is_heating && delta_floor_heating > CONFIG_SC_FLOOR_HEATING_DELTA_TEMP_HIGH) {
ESP_LOGI(SOLAR_LOGGER, "Start heating the floor from the store...");
floor_heating_pump_pwm = SC_PUMP_HALF_POWER;
is_heating = 1;
} else if (is_heating && delta_floor_heating < CONFIG_SC_FLOOR_HEATING_DELTA_TEMP_LOW) {
ESP_LOGI(SOLAR_LOGGER, "Stop heating the floor from the store...");
floor_heating_pump_pwm = SC_PUMP_NO_POWER;
is_heating = 0;
}
ESP_LOGI(SOLAR_LOGGER, "p:%2.1f°C, s.l:%2.1f°C, s.h:%2.1f°C, f.h:%2.1f°C, p.p: %3.0f%%, fh.p: %3.0f%%", readings[panel], readings[store_low], readings[store_high], readings[floor_heating], panel_pump_pwm / 2.55, floor_heating_pump_pwm / 2.55);
// Set new PWM duty cycle on each pump
ESP_ERROR_CHECK(ledc_set_duty(LEDC_HIGH_SPEED_MODE, SC_LEDC_CHANNEL_PANEL_PUMP, panel_pump_pwm));
ESP_ERROR_CHECK(ledc_set_duty(LEDC_HIGH_SPEED_MODE, SC_LEDC_CHANNEL_FLOOR_HEATING_PUMP, floor_heating_pump_pwm));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_HIGH_SPEED_MODE, SC_LEDC_CHANNEL_PANEL_PUMP));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_HIGH_SPEED_MODE, SC_LEDC_CHANNEL_FLOOR_HEATING_PUMP));
// Only publish data when connected to the MQTT broker and time is synchronized with NTP
if (IS_READY(MQTT_CONNECTED_BIT|TIME_SYNC_BIT)) {
mqtt_publish_data("solar_panel_temperature", (json_value){MQTT_TYPE_FLOAT, {.f = readings[panel]}});
mqtt_publish_data("floor_temperature", (json_value){MQTT_TYPE_FLOAT, {.f = readings[floor_heating]}});
mqtt_publish_data("store_higher_temperature", (json_value){MQTT_TYPE_FLOAT, {.f = readings[store_high]}});
mqtt_publish_data("store_lower_temperature", (json_value){MQTT_TYPE_FLOAT, {.f = readings[store_low]}});
mqtt_publish_data("panel_pump_duty_cycle", (json_value){MQTT_TYPE_INT, {.i = panel_pump_pwm}});
mqtt_publish_data("floor_heating_pump_duty_cycle", (json_value){MQTT_TYPE_INT, {.i = floor_heating_pump_pwm}});
}
vTaskDelayUntil(&last_wake_time, (1000 * CONFIG_SC_SAMPLE_PERIOD) / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
static void solar_pwm_init() {
// Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer_0 = {
.speed_mode = LEDC_HIGH_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_8_BIT,
.freq_hz = CONFIG_SC_PUMP_PWM_FREQ,
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer_0));
// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel_0 = {
.speed_mode = LEDC_HIGH_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = CONFIG_SC_PANEL_PUMP_GPIO,
.duty = 0, // Set duty to 0%
.hpoint = 0
};
ledc_channel_config_t ledc_channel_1 = {
.speed_mode = LEDC_HIGH_SPEED_MODE,
.channel = LEDC_CHANNEL_1,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = CONFIG_SC_FLOOR_HEATING_PUMP_GPIO,
.duty = 0, // Set duty to 0%
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_0));
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_1));
}
static int sensor_index(char * address) {
for (int i = 0; i < SC_EXPECTED_TEMPERATURE_SENSORS_COUNT; i++) {
char rom_code_s[17];
owb_string_from_rom_code(temperature_sensors[i].rom_code, rom_code_s, sizeof(rom_code_s));
if (strcmp(address, rom_code_s) == 0) {
return i;
}
}
return -1;
}
static void solar_ds18b20_init() {
// Stable readings require a brief period before communication
ESP_LOGI(SOLAR_LOGGER, "Waiting before 1-wire enumeration...");
vTaskDelay(2000.0 / portTICK_PERIOD_MS);
// Create a 1-Wire bus, using the RMT timeslot driver
owb = owb_rmt_initialize(&rmt_driver_info, CONFIG_SC_1WIRE_GPIO, RMT_CHANNEL_1, RMT_CHANNEL_0);
owb_use_crc(owb, true); // enable CRC check for ROM code
// Find all connected temperature sensors
ESP_LOGI(SOLAR_LOGGER, "List of all DS18B20 devices on bus:");
OneWireBus_ROMCode device_rom_codes[SC_MAX_TEMPERATURE_SENSORS_COUNT] = {0};
int num_devices = 0;
OneWireBus_SearchState search_state = {0};
bool found = false;
owb_search_first(owb, &search_state, &found);
while (found) {
char rom_code_s[17];
owb_string_from_rom_code(search_state.rom_code, rom_code_s, sizeof(rom_code_s));
ESP_LOGI(SOLAR_LOGGER, "device %d: %s", num_devices, rom_code_s);
device_rom_codes[num_devices] = search_state.rom_code;
++num_devices;
owb_search_next(owb, &search_state, &found);
}
ESP_LOGI(SOLAR_LOGGER, "Found %d device%s", num_devices, num_devices == 1 ? "" : "s");
if (num_devices != SC_EXPECTED_TEMPERATURE_SENSORS_COUNT) {
ESP_LOGE(SOLAR_LOGGER, "Cannot find exactly %d temperature sensors, rebooting!", SC_EXPECTED_TEMPERATURE_SENSORS_COUNT);
vTaskDelay(5000.0 / portTICK_PERIOD_MS);
esp_restart();
}
// Initializes all temperature sensors
for (int i = 0; i < num_devices; ++i)
{
ds18b20_init(&temperature_sensors[i], owb, device_rom_codes[i]); // associate with bus and device
ds18b20_use_crc(&temperature_sensors[i], true); // enable CRC check on all reads
ds18b20_set_resolution(&temperature_sensors[i], DS18B20_RESOLUTION_12_BIT); // use max. resolution
}
// Extract sensor addresses from NVS
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open("solar", NVS_READONLY, &nvs));
char * panel_sensor_addr = get_nvs_string(nvs, "panel_sensor");
assert(panel_sensor_addr != NULL);
char * store_higher_sensor_addr = get_nvs_string(nvs, "store_h_sensor");
assert(store_higher_sensor_addr != NULL);
char * store_lower_sensor_addr = get_nvs_string(nvs, "store_l_sensor");
assert(store_lower_sensor_addr != NULL);
char * floor_heating_sensor_addr = get_nvs_string(nvs, "fl_ht_sensor");
assert(floor_heating_sensor_addr != NULL);
if (panel_sensor_addr == NULL || store_higher_sensor_addr == NULL || store_lower_sensor_addr == NULL || floor_heating_sensor_addr == NULL) {
ESP_LOGE(SOLAR_LOGGER, "Cannot find one of the temperature sensor addresses, check your configuration!");
vTaskDelay(5000.0 / portTICK_PERIOD_MS);
esp_restart();
}
// Match each sensor with its function
panel = sensor_index(panel_sensor_addr);
assert(panel != -1);
store_high = sensor_index(store_higher_sensor_addr);
assert(store_high != -1);
store_low = sensor_index(store_lower_sensor_addr);
assert(store_low != -1);
floor_heating = sensor_index(floor_heating_sensor_addr);
assert(floor_heating != -1);
if (panel == -1 || store_high == -1 || store_low == -1 || floor_heating == -1) {
ESP_LOGE(SOLAR_LOGGER, "Cannot find one of the temperature sensors, check your configuration!");
vTaskDelay(5000.0 / portTICK_PERIOD_MS);
esp_restart();
}
}
void solar_init() {
solar_pwm_init();
solar_ds18b20_init();
// Create a task to handle solar monitoring
BaseType_t xReturned;
xReturned = xTaskCreate(solar_process,
"solar_process",
CONFIG_SC_STACK_SIZE, /* Stack size in words, not bytes. */
NULL, /* Parameter passed into the task. */
tskIDLE_PRIORITY + 12,
NULL);
if (xReturned != pdPASS) {
ESP_LOGE(SOLAR_LOGGER, "xTaskCreate('solar_process'): %d", xReturned);
abort();
}
}

7
main/solar.h

@ -0,0 +1,7 @@
#ifndef __SOLAR_H__
#define __SOLAR_H__
void solar_init();
void solar_set_heating(int enabled);
#endif

61
main/wifi.c

@ -0,0 +1,61 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "wifi.h"
#include "common.h"
static const char *WIFI_LOGGER = "wifi";
static void wifi_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) {
ESP_LOGI(WIFI_LOGGER,"Connection to the AP failed!");
xEventGroupClearBits(services_event_group, WIFI_CONNECTED_BIT);
ESP_LOGI(WIFI_LOGGER, "Retrying to connect to the AP...");
esp_wifi_connect();
} 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(WIFI_LOGGER, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(services_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
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,
&wifi_event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
&instance_got_ip));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(WIFI_LOGGER, "wifi_init_sta finished.");
}

6
main/wifi.h

@ -0,0 +1,6 @@
#ifndef __WIFI_H__
#define __WIFI_H__
void wifi_init_sta(void);
#endif

5
provision/.gitignore

@ -0,0 +1,5 @@
# contains sensitive data
sdkconfig
sdkconfig.dev
sdkconfig.prod
sdkconfig.defaults

6
provision/CMakeLists.txt

@ -0,0 +1,6 @@
# The following five 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(provisioner)

8
provision/Makefile

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

2
provision/README.md

@ -0,0 +1,2 @@
# Settings provisioner

2
provision/main/CMakeLists.txt

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

68
provision/main/Kconfig.projbuild

@ -0,0 +1,68 @@
menu "Provisioning data"
config ESP_WIFI_SSID
string "WiFi SSID"
default ""
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default ""
help
WiFi password (WPA or WPA2) for the example to use.
config ESP_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
config MQTT_URI
string "MQTT Server URI"
default "mqtts://server:port"
help
MQTT server location.
config MQTT_USERNAME
string "MQTT username"
default ""
help
MQTT username.
config MQTT_PASSWORD
string "MQTT password"
default ""
help
MQTT password.
config FIRMWARE_URL_PATTERN
string "Firmware URL pattern"
default ""
help
Where to download a specific version of the firmware. Complete URL. Must include "%s".
config PANEL_SENSOR_ADDR
string "1-wire address of the panel sensor"
default ""
help
1-wire address of the panel sensor.
config STORE_LOWER_SENSOR_ADDR
string "1-wire address of the store lower sensor"
default ""
help
1-wire address of the store lower sensor.
config STORE_HIGHER_SENSOR_ADDR
string "1-wire address of the store higher sensor"
default ""
help
1-wire address of the store higher sensor.
config FLOOR_HEATING_SENSOR_ADDR
string "1-wire address of the floor heating sensor"
default ""
help
1-wire address of the floor heating sensor.
endmenu

8
provision/main/component.mk

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

169
provision/main/provision.c

@ -0,0 +1,169 @@
#include <string.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "nvs_flash.h"
#include "nvs.h"
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* 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
static const char *TAG = "wifi station";
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 < CONFIG_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 nvs_dumpall() {
printf("NVS Dump: \n");
nvs_iterator_t it = nvs_entry_find("nvs", NULL, NVS_TYPE_ANY);
while (it != NULL) {
char * value = NULL;
char namespace[17];
memset(namespace, 0, 17);
nvs_entry_info_t info;
nvs_entry_info(it, &info);
memcpy(namespace, info.namespace_name, 16);
printf("%s/%s: type 0x%02x, value = %s\n", namespace, info.key, info.type, value ? value : "<UNKNOWN>");
it = nvs_entry_next(it);
};
}
void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
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 = CONFIG_ESP_WIFI_SSID,
.password = CONFIG_ESP_WIFI_PASSWORD,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
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",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open("mqtt", NVS_READWRITE, &nvs));
nvs_set_str(nvs, "url", CONFIG_MQTT_URI);
nvs_set_str(nvs, "username", CONFIG_MQTT_USERNAME);
nvs_set_str(nvs, "password", CONFIG_MQTT_PASSWORD);
nvs_close(nvs);
ESP_ERROR_CHECK(nvs_open("ota", NVS_READWRITE, &nvs));
nvs_set_str(nvs, "update_url", CONFIG_FIRMWARE_URL_PATTERN);
nvs_close(nvs);
ESP_ERROR_CHECK(nvs_open("solar", NVS_READWRITE, &nvs));
nvs_set_str(nvs, "panel_sensor", CONFIG_PANEL_SENSOR_ADDR);
nvs_set_str(nvs, "store_h_sensor", CONFIG_STORE_HIGHER_SENSOR_ADDR);
nvs_set_str(nvs, "store_l_sensor", CONFIG_STORE_LOWER_SENSOR_ADDR);
nvs_set_str(nvs, "fl_ht_sensor", CONFIG_FLOOR_HEATING_SENSOR_ADDR);
nvs_close(nvs);
nvs_dumpall();
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
}
void app_main(void)
{
//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
wifi_init_sta();
}

1
sdkconfig.defaults

@ -0,0 +1 @@
sdkconfig.dev

19
sdkconfig.dev

@ -0,0 +1,19 @@
CONFIG_MQTT_LWT_TOPIC="test/solar-controller/connected"
CONFIG_MQTT_SOLAR_VALUE_TOPIC="test/solar-controller/status/solar/%s"
CONFIG_MQTT_COMMAND_TOPIC="test/solar-controller/command"
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_TWO_OTA=y
CONFIG_LOG_DEFAULT_LEVEL=4
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_OTA_ALLOW_HTTP=y
CONFIG_SC_1WIRE_GPIO=4
CONFIG_SC_SAMPLE_PERIOD=1
CONFIG_SC_PANEL_PUMP_GPIO=19
CONFIG_SC_FLOOR_HEATING_PUMP_GPIO=21
CONFIG_SC_FROST_PROTECTION_TEMP_LOW=16
CONFIG_SC_FROST_PROTECTION_TEMP_HIGH=18
CONFIG_SC_PANEL_DELTA_TEMP_LOW=2
CONFIG_SC_PANEL_DELTA_TEMP_HIGH=3
CONFIG_SC_FLOOR_HEATING_DELTA_TEMP_LOW=2
CONFIG_SC_FLOOR_HEATING_DELTA_TEMP_HIGH=3

9
sdkconfig.prod

@ -0,0 +1,9 @@
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_TWO_OTA=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
# CONFIG_MQTT_TRANSPORT_WEBSOCKET is not set
# CONFIG_LWIP_IPV6 is not set
# CONFIG_ETH_USE_ESP32_EMAC is not set
# CONFIG_ETH_USE_SPI_ETHERNET is not set
Loading…
Cancel
Save