diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index d82ea1a..9837aca 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -1,46 +1,13 @@ menu "airmon Configuration" -config ONE_WIRE_GPIO - int "OneWire GPIO number" - range 0 33 - default 4 - help - GPIO number (IOxx) to access One Wire Bus. - - Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used. - - GPIOs 34-39 are input-only so cannot be used to drive the One Wire Bus. - -config ENABLE_STRONG_PULLUP_GPIO - bool "Enable strong pull-up controlled by GPIO (MOSFET)" - default n - help - An external circuit can be used to provide a strong pull-up to the One Wire Bus. - This is useful when the bus has parasitic-powered devices and extra current is - required to power them, such as during temperature ADC conversions. - - An example of such a circuit for the ESP32 is a P-channel MOSFET (such as the BS250) - connected Source-to-Drain between a current-limiting resistor (e.g. 270ohm for 12mA - max at 3.3V), itself connected to VCC, and the One Wire Bus data line. The Gate is - connected to the GPIO specified here. - -config STRONG_PULLUP_GPIO - int "Strong pull-up GPIO number" - range 0 33 - default 5 - help - GPIO number (IOxx) to control the strong pull-up on the One Wire Bus, perhaps - via a P-channel MOSFET between VCC and the One Wire Bus data line. - - This GPIO will be set as an output and driven high during temperature conversion. - This would enable the MOSFET providing current to the devices. - - At all other times it will be driven low, switching off the MOSFET and allowing - the One Wire Bus to operate normally. - - Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used. - - GPIOs 34-39 are input-only so cannot be used to drive the One Wire Bus. - depends on ENABLE_STRONG_PULLUP_GPIO - +config TEMPERATURE_PERIOD_SECONDS + int "Temperature measurement period, seconds" + range 1 3600 + default 15 + +config TEMPERATURE_KEEP_HOURS + int "How many hours of temperature measurements to keep in queue" + range 1 24 + default 3 endmenu + diff --git a/main/app_main.c b/main/app_main.c index 13335f3..31b9846 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -1,195 +1,190 @@ +#include "driver/gpio.h" #include "esp_log.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" +#include "freertos/queue.h" #include "freertos/task.h" +#include "sys/time.h" #include "ds18b20.h" #include "owb.h" #include "owb_rmt.h" -#define GPIO_DS18B20_0 (CONFIG_ONE_WIRE_GPIO) -#define MAX_DEVICES (8) -#define DS18B20_RESOLUTION (DS18B20_RESOLUTION_12_BIT) -#define SAMPLE_PERIOD (1000) // milliseconds +typedef struct { + const char *name; + const int pin; + const rmt_channel_t rx; + const rmt_channel_t tx; + QueueHandle_t queue; +} sensor_config; -_Noreturn void app_main() { - // Override global log level - esp_log_level_set("*", ESP_LOG_INFO); +typedef struct { + time_t time; + float temp; + const char *sensor; +} temp_measurement; - // To debug, use 'make menuconfig' to set default Log level to DEBUG, then - // uncomment: - // esp_log_level_set("owb", ESP_LOG_DEBUG); - // esp_log_level_set("ds18b20", ESP_LOG_DEBUG); - // esp_log_level_set("owb_rmt", ESP_LOG_DEBUG); +static sensor_config temp_sensors[] = { + {"room", 5, RMT_CHANNEL_0, RMT_CHANNEL_1, NULL}, + // {"street", 12, RMT_CHANNEL_2, RMT_CHANNEL_3, NULL}, +}; - // Stable readings require a brief period before communication - vTaskDelay(2000.0 / portTICK_PERIOD_MS); +static const int kSensorResolution = (DS18B20_RESOLUTION_12_BIT); - // Create a 1-Wire bus, using the RMT timeslot driver - owb_rmt_driver_info rmt_driver_info; +// one second in ticks +static const int kSecond = 1000 / portTICK_PERIOD_MS; - OneWireBus *owb = owb_rmt_initialize(&rmt_driver_info, GPIO_DS18B20_0, - RMT_CHANNEL_1, RMT_CHANNEL_0); +// delay between two temperature measurements +static const int kTempDelay = CONFIG_TEMPERATURE_PERIOD_SECONDS * kSecond; - owb_use_crc(owb, true); // enable CRC check for ROM code +// keep that many hours of temperature measurements in case internet goes down +static const int kTempQueueLength = + CONFIG_TEMPERATURE_KEEP_HOURS * 60 * 60 / CONFIG_TEMPERATURE_PERIOD_SECONDS; - // Find all connected devices - printf("Find devices:\n"); +static const char *const kTag = "airmon"; - OneWireBus_ROMCode device_rom_codes[MAX_DEVICES] = {0}; - OneWireBus_SearchState search_state = {0}; - int devices_found = 0; - bool found = false; +static DS18B20_Info *search_temp_sensor(const OneWireBus *const owb) { + for (;;) { + bool found = false; + OneWireBus_SearchState search_state = {0}; - owb_search_first(owb, &search_state, &found); + owb_search_first(owb, &search_state, &found); + if (found) { + break; + } - if (found) { - printf("found device: %d\n", search_state.last_device_flag); + ESP_LOGD(kTag, "temp sensor not found, retrying"); + vTaskDelay(500 / portTICK_PERIOD_MS); } - while (found) { - char rom_code_s[17]; - owb_string_from_rom_code(search_state.rom_code, rom_code_s, - sizeof(rom_code_s)); - printf(" %d : %s\n", devices_found, rom_code_s); - device_rom_codes[devices_found] = search_state.rom_code; - ++devices_found; - owb_search_next(owb, &search_state, &found); - } - printf("Found %d device%s\n", devices_found, devices_found == 1 ? "" : "s"); - - // In this example, if a single device is present, then the ROM code is - // probably not very interesting, so just print it out. If there are multiple - // devices, then it may be useful to check that a specific device is present. - - if (devices_found == 1) { - // For a single device only: - OneWireBus_ROMCode rom_code; - owb_status status = owb_read_rom(owb, &rom_code); - if (status == OWB_STATUS_OK) { - char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; - owb_string_from_rom_code(rom_code, rom_code_s, sizeof(rom_code_s)); - printf("Single device %s present\n", rom_code_s); - } else { - printf("An error occurred reading ROM code: %d", status); - } - } else { - // Search for a known ROM code (LSB first): - // For example: 0x1502162ca5b2ee28 - OneWireBus_ROMCode known_device = { - .fields.family = {0x28}, - .fields.serial_number = {0xee, 0xb2, 0xa5, 0x2c, 0x16, 0x02}, - .fields.crc = {0x15}, - }; - char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; - owb_string_from_rom_code(known_device, rom_code_s, sizeof(rom_code_s)); - bool is_present = false; + OneWireBus_ROMCode rom_code = {0}; + const owb_status status = owb_read_rom(owb, &rom_code); - owb_status search_status = owb_verify_rom(owb, known_device, &is_present); - if (search_status == OWB_STATUS_OK) { - printf("Device %s is %s\n", rom_code_s, - is_present ? "present" : "not present"); - } else { - printf("An error occurred searching for known device: %d", search_status); - } + if (status == OWB_STATUS_OK) { + char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; + owb_string_from_rom_code(rom_code, rom_code_s, sizeof(rom_code_s)); + ESP_LOGI(kTag, "found device %s", rom_code_s); + } else { + ESP_LOGE(kTag, "could not read ROM code: %d", status); + return NULL; } // Create DS18B20 devices on the 1-Wire bus - DS18B20_Info *devices[MAX_DEVICES] = {0}; - for (int i = 0; i < devices_found; ++i) { - DS18B20_Info *ds18b20_info = ds18b20_malloc(); // heap allocation - devices[i] = ds18b20_info; - - if (devices_found == 1) { - printf("Single device optimisations enabled\n"); - ds18b20_init_solo(ds18b20_info, owb); // only one device on bus + DS18B20_Info *const device = ds18b20_malloc(); // heap allocation + ds18b20_init_solo(device, owb); // only one device on bus + ds18b20_use_crc(device, true); // enable CRC check on all reads + ds18b20_set_resolution(device, kSensorResolution); + + return device; +} + +static OneWireBus *initialize_bus(owb_rmt_driver_info *const driver_info, + const sensor_config *const config) { + OneWireBus *owb = + owb_rmt_initialize(driver_info, config->pin, config->tx, config->rx); + + owb_use_crc(owb, true); // enable CRC check for ROM code + + return owb; +} + +static void run_temp_measurements(const DS18B20_Info *const device, + const sensor_config *const config) { + int error_count = 0; + + while (error_count < 10) { + TickType_t last_wake_time = xTaskGetTickCount(); + + temp_measurement measurement; + const DS18B20_ERROR err = + ds18b20_convert_and_read_temp(device, &measurement.temp); + + if (err != DS18B20_OK) { + ESP_LOGW(kTag, "measurement failed in sensor %s", config->name); + ++error_count; } else { - ds18b20_init(ds18b20_info, owb, - device_rom_codes[i]); // associate with bus and device + error_count = 0; + + struct timeval tm; + gettimeofday(&tm, NULL); + + measurement.sensor = config->name; + measurement.time = tm.tv_sec; + + BaseType_t sent; + do { + sent = xQueueSendToBack(config->queue, &measurement, kSecond); + if (sent == errQUEUE_FULL) { + temp_measurement buf; + xQueueReceive(config->queue, &buf, 0); + } + } while (sent == errQUEUE_FULL); } - ds18b20_use_crc(ds18b20_info, true); // enable CRC check on all reads - ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION); - } - // // Read temperatures from all sensors sequentially - // while (1) - // { - // printf("\nTemperature readings (degrees C):\n"); - // for (int i = 0; i < num_devices; ++i) - // { - // float temp = ds18b20_get_temp(devices[i]); - // printf(" %d: %.3f\n", i, temp); - // } - // vTaskDelay(1000 / portTICK_PERIOD_MS); - // } - - // Check for parasitic-powered devices - bool parasitic_power = false; - ds18b20_check_for_parasite_power(owb, ¶sitic_power); - if (parasitic_power) { - printf("Parasitic-powered devices detected"); + vTaskDelayUntil(&last_wake_time, kTempDelay); } +} - // In parasitic-power mode, devices cannot indicate when conversions are - // complete, so waiting for a temperature conversion must be done by waiting a - // prescribed duration - owb_use_parasitic_power(owb, parasitic_power); - -#ifdef CONFIG_ENABLE_STRONG_PULLUP_GPIO - // An external pull-up circuit is used to supply extra current to OneWireBus - // devices during temperature conversions. - owb_use_strong_pullup_gpio(owb, CONFIG_STRONG_PULLUP_GPIO); -#endif - - // Read temperatures more efficiently by starting conversions on all devices - // at the same time - int errors_count[MAX_DEVICES] = {0}; - int sample_count = 0; - if (devices_found > 0) { - TickType_t last_wake_time = xTaskGetTickCount(); +_Noreturn void collect_temps(const sensor_config *const config) { + ESP_LOGI(kTag, "starting temp collection task for %s", config->name); - while (1) { - last_wake_time = xTaskGetTickCount(); + for (;;) { + vTaskDelay(2 * kSecond); - ds18b20_convert_all(owb); + owb_rmt_driver_info rmt_driver_info; + initialize_bus(&rmt_driver_info, config); - // In this application all devices use the same resolution, - // so use the first device to determine the delay - ds18b20_wait_for_conversion(devices[0]); + OneWireBus *const owb = &rmt_driver_info.bus; + DS18B20_Info *device = search_temp_sensor(owb); - // Read the results immediately after conversion otherwise it may fail - // (using printf before reading may take too long) - float readings[MAX_DEVICES] = {0}; - DS18B20_ERROR errors[MAX_DEVICES] = {0}; + run_temp_measurements(device, config); + ESP_LOGE(kTag, "sensor %s failed, restarting", config->name); - for (int i = 0; i < devices_found; ++i) { - errors[i] = ds18b20_read_temp(devices[i], &readings[i]); - } + // clean up dynamically allocated data + ds18b20_free(&device); + owb_uninitialize(owb); + } +} - // Print results in a separate loop, after all have been read - printf("\nTemperature readings (degrees C): sample %d\n", ++sample_count); - for (int i = 0; i < devices_found; ++i) { - if (errors[i] != DS18B20_OK) { - ++errors_count[i]; - } +_Noreturn void app_main() { + esp_log_level_set("*", ESP_LOG_INFO); + + // To debug, use 'make menuconfig' to set default Log level to DEBUG, then + // uncomment: + // esp_log_level_set("owb", ESP_LOG_DEBUG); + // esp_log_level_set("ds18b20", ESP_LOG_DEBUG); + // esp_log_level_set("owb_rmt", ESP_LOG_DEBUG); + + QueueHandle_t temp_queue = + xQueueCreate(kTempQueueLength, sizeof(temp_measurement)); + + if (!temp_queue) { + ESP_LOGW(kTag, "trying a smaller temp queue"); - printf(" %d: %.1f %d errors\n", i, readings[i], errors_count[i]); - } + temp_queue = xQueueCreate(10, sizeof(temp_measurement)); - vTaskDelayUntil(&last_wake_time, SAMPLE_PERIOD / portTICK_PERIOD_MS); + if (!temp_queue) { + ESP_LOGE(kTag, "temp queue could not be created"); + esp_restart(); } - } else { - printf("\nNo DS18B20 devices detected!\n"); } - // clean up dynamically allocated data - for (int i = 0; i < devices_found; ++i) { - ds18b20_free(&devices[i]); + for (int i = 0; i < sizeof(temp_sensors) / sizeof(*temp_sensors); ++i) { + char name[24]; + + sensor_config *conf = &temp_sensors[i]; + conf->queue = temp_queue; + + snprintf(name, sizeof(name), "temperature_%d", conf->pin); + + xTaskCreate((TaskFunction_t)collect_temps, name, 2048, conf, 1, NULL); } - owb_uninitialize(owb); - printf("Restarting now.\n"); - fflush(stdout); - vTaskDelay(1000 / portTICK_PERIOD_MS); - esp_restart(); + for (;;) { + temp_measurement temp = {0}; + + if (xQueueReceive(temp_queue, &temp, portMAX_DELAY)) { + printf("%s: %.1f°C\n", temp.sensor, temp.temp); + } + } }