--- /dev/null
+idf_component_register(SRCS "esp-aht30.c"
+ INCLUDE_DIRS "include"
+ REQUIRES esp_driver_i2c)
--- /dev/null
+
+Copyright (C) 2024 Michael Zucchi
+
+See the section LICENSE for details.
+
+Introduction
+------------
+
+esp-aht30 is an i2c driver for the ASAIR AHT30 temperature and
+humidity sensor.
+
+Compile
+-------
+
+This is an esp-idf component which needs to be copied to the
+components directory of your project or otherwise referenced by the
+esp-idf build system for your project.
+
+Then add REQUIRES esp-aht30 to idf_component_register() for the
+project.
+
+Usage
+-----
+
+See the header.
+
+Links
+-----
+
+* browse source <https://code.zedzone.au/cvs?p=esp-aht30&a=tree>
+ source code <https://code.zedzone.au/git/esp-aht30>
+
+* esp-idf <https://github.com/espressif/esp-idf>
+
+* AHT30 <https://asairsensors.com/product/aht30-integrated-temperature-and-humidity-sensor/>
+
+LICENSE
+-------
+
+This code is licensed under GNU General Public License version
+3, see COPYING for the full details.
+
+ Copyright (C) 2024 Michael Zucchi
+
+ This program is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
--- /dev/null
+#include <stdio.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#include "esp-aht30.h"
+
+static const uint8_t aht30_update_seq[] = { 0xac, 0x33, 0x00 };
+
+static uint8_t crc8(uint8_t *msg, size_t n) {
+ uint8_t crc = 0xff;
+ uint8_t *end = msg + n;
+
+ while (msg < end) {
+ crc ^= *msg++;
+ for (int i=0;i<8;i++) {
+ if (crc & 0x80)
+ crc = (crc << 1) ^ 0x31;
+ else
+ crc = (crc << 1);
+ }
+ }
+
+ return crc;
+}
+
+esp_err_t aht30_init(i2c_master_bus_handle_t bus, const aht30_config_t *config, aht30_handle_t *aht) {
+ i2c_device_config_t i2c_dev_conf = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = AHT30_ADDR,
+ .scl_speed_hz = config ? config->scl_speed_hz : 400000,
+ .scl_wait_us = config ? config->scl_wait_us : 20000,
+ };
+
+ return i2c_master_bus_add_device(bus, &i2c_dev_conf, &aht->dev);
+}
+
+esp_err_t aht30_delete(aht30_handle_t *aht) {
+ return i2c_master_bus_rm_device(aht->dev);
+}
+
+esp_err_t aht30_read(aht30_handle_t *aht, aht30_reading_t *res) {
+ uint8_t data[7];
+
+ // request sensor update
+ ESP_ERROR_CHECK(i2c_master_transmit(aht->dev, aht30_update_seq, sizeof(aht30_update_seq), 500));
+
+ // delay required 80ms
+ vTaskDelay(80 / portTICK_PERIOD_MS);
+
+ // poll result
+ ESP_ERROR_CHECK(i2c_master_receive(aht->dev, data, 7, 500));
+
+ res->status = data[0];
+ res->valid = crc8(data, 6) == data[6];
+ res->humidity = (data[1] << 12 | data[2] << 4 | data[3] >> 4) * 100;
+ res->temperature = ((data[3] << 16 | data[4] << 8 | data[5]) & 0xfffff) * 200 - (50 << 20);
+
+ return 0;
+}
--- /dev/null
+#ifndef ESP_AHT30_H
+#define ESP_AHT30_H
+
+#include "driver/i2c_master.h"
+
+struct aht30_reading {
+ uint8_t status;
+ uint8_t valid; /* if crc check passed */
+ int32_t temperature; /* deg C, fixed point 12.20 */
+ uint32_t humidity; /* RH%, fixed point 12.20 */
+};
+
+typedef struct aht30_reading aht30_reading_t;
+
+#define AHT30_ADDR (0x38)
+#define AHT30_STATUS_BUSY 0x80
+#define AHT30_STATUS_CRC_PASSED 0x10
+#define AHT30_STATUS_CALIBRATED 0x08
+
+struct aht30_config {
+ uint32_t scl_speed_hz;
+ uint32_t scl_wait_us;
+};
+
+typedef struct aht30_config aht30_config_t;
+
+struct aht30_handle {
+ i2c_master_dev_handle_t dev;
+};
+
+typedef struct aht30_handle aht30_handle_t;
+
+esp_err_t aht30_init(i2c_master_bus_handle_t bus, const aht30_config_t *config, aht30_handle_t *handle);
+esp_err_t aht30_delete(aht30_handle_t *aht);
+
+esp_err_t aht30_read(aht30_handle_t *aht, aht30_reading_t *res);
+
+#endif