commit
33ae8b7294
35 changed files with 1345 additions and 0 deletions
@ -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 |
|||
@ -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 |
|||
@ -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) |
|||
@ -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 |
|||
@ -0,0 +1,2 @@ |
|||
# Solar Controller |
|||
|
|||
@ -0,0 +1,2 @@ |
|||
idf_component_register(SRCS "main.c" "solar.c" "wifi.c" "mqtt.c" "sntp.c" "common.c" "ota.c" |
|||
INCLUDE_DIRS ".") |
|||
@ -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 |
|||
@ -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----- |
|||
@ -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; |
|||
} |
|||
@ -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 |
|||
@ -0,0 +1,4 @@ |
|||
#
|
|||
# Main Makefile. This is basically the same as a component makefile.
|
|||
#
|
|||
COMPONENT_EMBED_TXTFILES := cacert.pem |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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 |
|||
@ -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); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
#ifndef __OTA_H__ |
|||
#define __OTA_H__ |
|||
|
|||
void trigger_ota_update(char* version); |
|||
|
|||
#endif |
|||
@ -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(); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
#ifndef __SNTP_H__ |
|||
#define __SNTP_H__ |
|||
|
|||
void sntp_start(); |
|||
|
|||
#endif |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
#ifndef __SOLAR_H__ |
|||
#define __SOLAR_H__ |
|||
|
|||
void solar_init(); |
|||
void solar_set_heating(int enabled); |
|||
|
|||
#endif |
|||
@ -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."); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
#ifndef __WIFI_H__ |
|||
#define __WIFI_H__ |
|||
|
|||
void wifi_init_sta(void); |
|||
|
|||
#endif |
|||
@ -0,0 +1,5 @@ |
|||
# contains sensitive data |
|||
sdkconfig |
|||
sdkconfig.dev |
|||
sdkconfig.prod |
|||
sdkconfig.defaults |
|||
@ -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) |
|||
@ -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 |
|||
@ -0,0 +1,2 @@ |
|||
# Settings provisioner |
|||
|
|||
@ -0,0 +1,2 @@ |
|||
idf_component_register(SRCS "provision.c" |
|||
INCLUDE_DIRS ".") |
|||
@ -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 |
|||
@ -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.
|
|||
#
|
|||
@ -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(); |
|||
} |
|||
@ -0,0 +1 @@ |
|||
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 |
|||
@ -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…
Reference in new issue